Measure Python program execution time

Use perf_counter for elapsed time, timeit for repeated small benchmarks, and a timer that still reports when the measured code raises.

Best practices
Python programming and application logging

Measure elapsed execution time by subtracting two readings from time.perf_counter(). It is intended for durations and includes time spent waiting or sleeping. Wall-clock timestamps from time.time() answer a different question and can change when the system clock is adjusted.

Time a block of code

import time

start = time.perf_counter()
result = sum(range(1_000_000))
elapsed = time.perf_counter() - start
print(f"Elapsed: {elapsed:.6f} seconds")

The counter's absolute value has no calendar meaning. For integer nanosecond units, use perf_counter_ns() on Python 3.7+. That avoids floating-point representation loss; it does not guarantee nanosecond hardware accuracy. Inspect time.get_clock_info("perf_counter") for the clock's reported properties.

Know the limitation of time.time()

import time

started_at = time.time()
result = sum(range(10_000))
finished_at = time.time()
print(started_at, finished_at)

Those values are useful wall-clock timestamps. Their difference can be distorted by clock corrections, so prefer perf_counter() for a duration, even when a simple subtraction appears to work.

Benchmark a small operation with timeit

import timeit

repeats = timeit.repeat("sum(range(1000))", number=10_000, repeat=5)
best_per_call = min(repeats) / 10_000
print(f"Best observed time per call: {best_per_call * 1000:.6f} ms")

Each result is the total for 10,000 executions. Repetition helps reveal variability; it does not remove all noise or make a microbenchmark representative of production. Keep setup, input sizes, and interpreter versions comparable. timeit disables garbage collection by default during timing, which can matter for allocation-heavy workloads.

Reuse a timer around a larger operation

from contextlib import contextmanager
import time

@contextmanager
def timer(name):
    start = time.perf_counter()
    try:
        yield
    finally:
        print(f"{name}: {time.perf_counter() - start:.6f}s")

with timer("Processing"):
    result = sum(range(1_000_000))

The finally block reports elapsed time even when the measured code raises; it does not suppress that exception. A similar wrapper can be implemented as a decorator when you need function-level measurements.

FAQ

How do I measure async work? Start the timer before the await and stop it afterward. That measures elapsed time, including waits. Merely creating a coroutine object does not execute it.

What about CPU time? time.process_time() measures the process's CPU time and excludes sleep. Choose it when that is the metric you need.

How do I find the slow function or line? cProfile attributes time to function calls. A tool such as line_profiler provides line-level profiling; cProfile itself is not a per-line timer.

References

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting