ZetCode

Python time.monotonic_ns Function

Last modified April 11, 2025

This comprehensive guide explores Python's time.monotonic_ns function, which returns a nanosecond-resolution monotonic clock value. We'll cover precise timing, performance measurement, and practical examples.

Basic Definitions

The time.monotonic_ns function returns a clock value in nanoseconds. It is monotonic (never decreases) and unaffected by system clock adjustments.

Key characteristics: nanosecond resolution, ideal for precise timing intervals, and guaranteed to never go backward. The reference point is undefined but consistent during program execution.

Basic Usage of monotonic_ns

This example demonstrates the basic usage of time.monotonic_ns to measure time intervals with nanosecond precision.

basic_monotonic.py
import time

# Get current monotonic time in nanoseconds
start = time.monotonic_ns()

# Simulate some work
time.sleep(0.5)  # Sleep for 500 milliseconds

# Get end time
end = time.monotonic_ns()

# Calculate duration in nanoseconds
duration_ns = end - start
duration_ms = duration_ns / 1_000_000  # Convert to milliseconds

print(f"Operation took {duration_ns} nanoseconds")
print(f"Which is {duration_ms:.2f} milliseconds")

This example shows how to measure time intervals with nanosecond precision. The sleep duration is accurately measured despite system clock changes.

Note that while the resolution is in nanoseconds, the actual precision depends on the underlying system capabilities.

Comparing monotonic_ns with monotonic

This example compares time.monotonic_ns with time.monotonic to show the difference in precision and representation.

comparison.py
import time

def test_function():
    sum(range(1_000_000))

# Using time.monotonic()
start = time.monotonic()
test_function()
end = time.monotonic()
print(f"time.monotonic(): {(end - start) * 1e9:.0f} ns")

# Using time.monotonic_ns()
start = time.monotonic_ns()
test_function()
end = time.monotonic_ns()
print(f"time.monotonic_ns(): {end - start} ns")

Both functions use the same underlying clock, but monotonic_ns provides direct access to nanosecond values without floating-point conversion.

The monotonic_ns version avoids potential floating-point precision issues for very precise measurements.

Microbenchmarking with monotonic_ns

This example demonstrates using time.monotonic_ns for microbenchmarking small code segments with high precision.

microbenchmark.py
import time

def benchmark(func, iterations=1_000_000):
    start = time.monotonic_ns()
    for _ in range(iterations):
        func()
    end = time.monotonic_ns()
    ns_per_op = (end - start) / iterations
    print(f"{func.__name__}: {ns_per_op:.2f} ns/operation")

def empty_function():
    pass

def simple_math():
    42 * 42

benchmark(empty_function)
benchmark(simple_math)

This pattern is useful for measuring the performance of very small operations. The nanosecond resolution allows accurate timing of individual operations.

Note that microbenchmarks can be affected by various system factors and should be interpreted carefully.

Precise Rate Limiting

This example implements a high-precision rate limiter using time.monotonic_ns to control operation frequency with nanosecond accuracy.

rate_limiter.py
import time

class NanoRateLimiter:
    def __init__(self, operations_per_second):
        self.interval_ns = 1_000_000_000 // operations_per_second
        self.last_run = 0
    
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            now = time.monotonic_ns()
            elapsed = now - self.last_run
            if elapsed < self.interval_ns:
                delay_ns = self.interval_ns - elapsed
                time.sleep(delay_ns / 1e9)  # Convert to seconds
            self.last_run = time.monotonic_ns()
            return func(*args, **kwargs)
        return wrapper

@NanoRateLimiter(1000)  # 1000 operations per second
def high_freq_operation():
    print("Operation at", time.monotonic_ns() % 1_000_000)

for _ in range(5):
    high_freq_operation()

The rate limiter ensures operations don't exceed the specified rate by precisely measuring intervals in nanoseconds.

This is particularly useful for hardware control or high-frequency APIs where precise timing is critical.

Measuring Small Code Blocks

This example shows how to measure very small code blocks using time.monotonic_ns with a context manager for convenience.

timing_context.py
import time
from contextlib import contextmanager

@contextmanager
def time_ns():
    start = time.monotonic_ns()
    yield
    end = time.monotonic_ns()
    print(f"Duration: {end - start} ns")

# Measure list comprehension
with time_ns():
    squares = [x*x for x in range(1000)]

# Measure dictionary creation
with time_ns():
    d = {x: x*x for x in range(1000)}

The context manager automatically measures and prints the execution time of the code block within the with statement.

This pattern makes it easy to add precise timing to existing code without cluttering the logic with timing code.

Long-running Process Monitoring

This example demonstrates using time.monotonic_ns to monitor a long-running process with periodic progress updates.

progress_monitor.py
import time

def long_running_task():
    total_items = 1_000_000
    start_time = time.monotonic_ns()
    last_report = start_time
    
    for i in range(total_items):
        # Simulate work
        time.sleep(0.001)
        
        # Report progress every second
        current_time = time.monotonic_ns()
        if current_time - last_report >= 1_000_000_000:
            elapsed_s = (current_time - start_time) / 1_000_000_000
            progress = (i + 1) / total_items * 100
            print(f"{elapsed_s:.1f}s: {progress:.1f}% complete")
            last_report = current_time
    
    total_time = (time.monotonic_ns() - start_time) / 1_000_000_000
    print(f"Task completed in {total_time:.2f} seconds")

long_running_task()

The example uses nanosecond timing to precisely control progress reporting intervals while avoiding drift from floating-point accumulation errors.

This approach ensures accurate timing even for very long-running processes where small timing errors could accumulate significantly.

Best Practices

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.