PythonMastery

How do I measure how long my Python code takes to run?

time.perf_counter() before and after, then subtract: it's the most precise clock Python has. To compare two small snippets fairly, use timeit.

Read time.perf_counter() before and after, and subtract.

python
import time

start = time.perf_counter()
total = sum(n * n for n in range(1_000_000))
elapsed = time.perf_counter() - start

print(f"took {elapsed:.3f} seconds")
example output · yours will differ
took 0.065 seconds

perf_counter is the clock built for this: it has the highest resolution available and never jumps. time.time() follows the wall clock, which can move backwards when your computer syncs its time, and then a measurement comes out negative.

Timing a function every time it runs

A small decorator saves pasting the same three lines everywhere:

python
import time
from functools import wraps

def timed(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@timed
def build_report(rows):
    return sorted(range(rows), reverse=True)[:5]

print(build_report(200_000))
+ setup added so this can run · defines args, kwargs
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

args = _AutoMock('args')
kwargs = _AutoMock('kwargs')
example output · yours will differ
build_report took 0.0052s
[199999, 199998, 199997, 199996, 199995]

Comparing two ways of doing something: timeit

A single run of a fast snippet is mostly noise. timeit runs each version many times so the comparison is fair. Compare the time per run, which is why each total is divided by its repeat count:

python
import timeit

items = list(range(10_000))
as_set = set(items)

# A set lookup is so quick that a thousand of them can finish inside one tick of
# the browser's clock, so the set gets far more repeats before dividing out.
per_list = timeit.timeit(lambda: 9_999 in items, number=1_000) / 1_000
per_set = timeit.timeit(lambda: 9_999 in as_set, number=500_000) / 500_000

print(f"list: {per_list * 1e6:.1f} µs per lookup   set: {per_set * 1e6:.3f} µs per lookup")
print(f"the set is about {per_list / per_set:,.0f}x faster here")
example output · yours will differ
list: 63.4 µs per lookup   set: 0.045 µs per lookup
the set is about 1,408x faster here

Those numbers are from a desktop. Yours will differ, and in the browser everything runs slower and even the ratio moves. What holds everywhere is the size of the gap: the set wins by orders of magnitude, not by a few per cent. In a Jupyter notebook, %timeit expression does all of this in one line.

Where is the time going?

When a whole program is slow and you don't know which part, timing pieces by hand is guesswork. python -m cProfile -s cumtime your_script.py lists every function with the time spent inside it, slowest first. The performance lesson walks through reading that output.

Go deeper: Performance: Profile, Then Optimise, Decorators: Functions That Wrap Functions

More short answers

all questions