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.
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.
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.
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.
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.
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.
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
- Precision needs: Use monotonic_ns when nanosecond resolution is required
- Long durations: Be mindful of integer overflow with very long measurements
- System limitations: Actual precision depends on OS and hardware
- Clock source: Uses the best available monotonic clock on the system
- Conversion: Divide by 1e9 for seconds when needed
Source References
Author
List all Python tutorials.