A lightweight benchmarking library with decorator and context manager APIs, auto‑calibrating timing, and built‑in run comparison.
$ pip install pybench
Everything you need to measure Python performance. Nothing you don't.
Pure stdlib. Uses time.perf_counter_ns() and statistics.
Optional rich for pretty output.
@benchmark decorator for functions.
bench.measure() context manager for inline blocks.
Use both together.
Automatically determines optimal iteration count. Disables GC during measurement. Nanosecond precision.
pybench run discovers and executes benchmark files.
Table or JSON output. Save results to disk.
Compare benchmark runs over time. Detect regressions in CI. Color-coded percentage changes.
Machine-readable output with platform metadata. Perfect for CI/CD pipelines and tracking performance over time.
pybench requires Python 3.10 or later. Install from PyPI:
$ pip install pybench
For pretty terminal output with colored tables, install with the rich extra:
$ pip install pybench[rich]
pybench provides two ways to define benchmarks: decorators for functions and context managers for inline blocks.
Decorate functions with @benchmark to register them for benchmarking.
The function will be called repeatedly with automatic warmup and iteration calibration.
import pybench bench = Bench() @bench.benchmark def sort_list(): sorted([3, 1, 4, 1, 5, 9, 2, 6] * 1000) @bench.benchmark(warmup=10, iterations=200) def hash_string(): hash("hello" * 1000) bench.run() bench.report()
Use @bench.benchmark without parentheses for default settings,
or pass warmup and iterations to override.
Use bench.measure() to time arbitrary blocks of code inline.
Ideal for comparing different approaches side by side.
from pybench import Bench bench = Bench() with bench.measure("list comprehension"): [x ** 2 for x in range(10_000)] with bench.measure("map + lambda"): list(map(lambda x: x ** 2, range(10_000))) bench.report()
Decorators and context managers work together on the same Bench instance.
All results are collected and reported together.
from pybench import Bench bench = Bench(warmup=5, iterations=100) @bench.benchmark def dict_merge(): {**{"a": 1}, **{"b": 2}} with bench.measure("dict union"): {"a": 1} | {"b": 2} results = bench.run() # Print a formatted table bench.report() # Or get JSON for CI json_str = bench.to_json()
bench.report() prints a formatted table to stdout:
pybench includes a CLI that discovers and runs benchmark files automatically.
Files matching bench_*.py or *_bench.py are discovered.
Discover and run benchmarks in the current directory or a specific path.
# Run all benchmarks in current directory $ pybench run # Run benchmarks in a specific directory $ pybench run benchmarks/ # Run a single benchmark file $ pybench run bench_sorting.py # Output JSON instead of table $ pybench run --json # Save results to a file for later comparison $ pybench run --save results.json # Control warmup and iteration count $ pybench run --warmup 10 --iterations 500
Benchmark files use the module-level @pybench.benchmark decorator.
The CLI imports each file and collects decorated functions.
import pybench data = list(range(1000, 0, -1)) @pybench.benchmark def builtin_sort(): sorted(data) @pybench.benchmark def reverse_then_sort(): d = data[:] d.reverse() d.sort()
Compare two saved benchmark runs to detect performance regressions or improvements.
# Save a baseline $ pybench run --save baseline.json # ... make changes ... # Save current run and compare $ pybench run --save current.json $ pybench compare baseline.json current.json
Complete reference for all public classes, methods, and functions.
Bench(warmup=5, iterations=None, target_time_ns=1_000_000_000)
Central orchestrator for collecting and running benchmarks.
| Parameter | Type | Default | Description |
|---|---|---|---|
| warmup | int | 5 | Warmup iterations before measurement |
| iterations | int | None | None | Fixed iteration count, or None for auto-calibration |
| target_time_ns | int | 1B | Target total time for auto-calibration (~1 second) |
Decorator to register a function as a benchmark. Can be used with or without parentheses.
Per-benchmark warmup and iterations override the Bench-level defaults.
Context manager that times the enclosed block and records a single-sample result. Returns immediately — timing happens on context exit.
Execute all registered benchmarks and return a list of results. Context manager results are included automatically.
Print results to stdout as a formatted table (default) or JSON.
Automatically calls run() if benchmarks haven't been executed yet.
Return results as a JSON string with platform metadata (Python version, OS, timestamp).
Automatically calls run() if benchmarks haven't been executed yet.
Immutable result object containing timing data and computed statistics.
Created by Bench.run() — you typically don't construct these directly.
| Field | Type | Description |
|---|---|---|
| name | str | Benchmark name |
| times_ns | list[int] | Raw timing data in nanoseconds |
| iterations | int | Number of measured iterations |
| mean_ns | float | Arithmetic mean of timings |
| median_ns | float | Median timing |
| stddev_ns | float | Standard deviation |
| min_ns | int | Fastest timing |
| max_ns | int | Slowest timing |
| ops_per_sec | float | Operations per second (1B / mean_ns) |
Module-level decorator for use with the CLI. Functions decorated with
@pybench.benchmark are discovered and run by pybench run.
import pybench @pybench.benchmark def my_benchmark(): # This function will be discovered by `pybench run` sorted(range(1000, 0, -1))
bench.to_json() and pybench run --json produce this structure:
{
"metadata": {
"python_version": "3.12.1",
"platform": "Linux",
"timestamp": "2026-02-13T12:00:00+00:00"
},
"results": [
{
"name": "sort_list",
"iterations": 1000,
"mean_ns": 142300.0,
"median_ns": 141800.0,
"stddev_ns": 2100.0,
"min_ns": 138900,
"max_ns": 149200,
"ops_per_sec": 7027.4
}
]
}