Skip to main content

BenchCore

[!IMPORTANT] BenchCore is pre-alpha. A minimal benchmarking API exists for experimentation, but it is not ready for production use and may change without notice.

BenchCore is a strongly typed, framework-agnostic benchmarking engine for Python. Its goal is to provide the measurement primitives needed by test framework integrations without making the core depend on any test runner.

The project grew out of a desire for a benchmarking codebase where typing, maintainability, and ease of contribution are foundational constraints. Existing tools such as pytest-benchmark helped demonstrate the value of benchmarking in developer workflows. BenchCore is an independent implementation with a broader architectural goal: a reusable engine that can eventually serve pytest, unittest, and other integrations.

BenchCore is not a testing framework, a test runner, or a drop-in replacement for pytest-benchmark. It will measure code; the surrounding framework will remain responsible for discovering and running tests.

Design goals

  • A small, stable, and strongly typed public API.
  • Strict Pyright compatibility without exposing Any in public interfaces.
  • No runtime dependency on pytest or another testing framework.
  • Replaceable timers, calibration, statistics, reporting, and storage components.
  • Near-zero runtime dependencies and low measurement overhead.
  • An approachable codebase designed for long-term maintenance.
  • Support for Python 3.12 and newer.

These are design constraints, not claims about features already implemented.

Intended experience

The eventual standalone API should be simple enough to look like this:

from benchcore import Bench

bench = Bench()
result = bench.run(sorted, [5, 4, 3, 2, 1])

print(result.statistics.mean_ns)

Or, for the common case:

from benchcore import benchmark

result = benchmark(sorted, [5, 4, 3, 2, 1])

Both examples are implemented by the current MVP. Configuration is available through Bench:

from benchcore import Bench, BenchmarkConfig

bench = Bench(BenchmarkConfig(rounds=20, warmup_rounds=2))
result = bench.run(sorted, [5, 4, 3, 2, 1])

By default, BenchCore calibrates the iteration count until a round reaches a target duration. Calibration samples are discarded before warmup and measurement:

config = BenchmarkConfig(
    target_round_time_ns=10_000_000,
    max_iterations=1_000_000,
    max_calibration_time_ns=1_000_000_000,
)
result = Bench(config).run(sorted, [5, 4, 3, 2, 1])

print(result.iterations)

Set iterations explicitly to disable calibration when a fixed count is required:

config = BenchmarkConfig(iterations=100)

max_calibration_time_ns is an accumulated calibration budget, not a hard timeout. BenchCore cannot interrupt a running callable, so one slow invocation may exceed the budget before calibration stops.

Fixed iterations are preferable when repeated calls change workload cost, mutate shared state, consume a finite input, or when reproducing an earlier run with an exact count. Automatic calibration is intended for callables whose cost remains reasonably stable across repeated invocations.

Round lifecycle

Optional hooks can prepare and restore state around every calibration, warmup, and measured round:

values = [3, 2, 1]


def reset_values() -> None:
    values[:] = [3, 2, 1]


bench = Bench(
    BenchmarkConfig(iterations=1),
    round_setup=reset_values,
    round_teardown=values.clear,
)
result = bench.run(values.sort)

round_setup and round_teardown are outside the timed interval. Once setup succeeds, teardown runs exactly once even when the benchmark callable or timer fails. If setup itself fails, teardown does not run. A teardown exception propagates and no partial result is returned.

BenchCore does not modify garbage collection or serialize concurrent runs. Hooks, callables, arguments, and injected timers must provide any thread safety and process-global state restoration they require.

Running the example

Install the development environment and execute the standalone example:

poetry install
poetry run python examples/basic.py

The reported durations are normalized per iteration and stored in nanoseconds. The example uses format_duration_ns() to select a readable display unit without changing the stored numeric data. Benchmark values vary between machines and even between runs on the same machine; compare results only under controlled conditions.

Results and units

Duration-bearing names include their unit explicitly. Whole measured regions use integer nanoseconds; normalized durations and statistics use floats because one iteration may represent a fraction of a timer tick:

from benchcore import format_duration_ns

print(result.total_time_ns)
print(result.statistics.mean_ns)
print(format_duration_ns(result.statistics.mean_ns))

standard_deviation_ns is the sample standard deviation across measured rounds. It uses Bessel's correction and is 0.0 when only one round exists. These descriptive statistics summarize observed runtime noise; they do not establish statistical significance or prove that one implementation is faster.

Quartiles, percentiles, and outlier labels are intentionally deferred until BenchCore defines minimum sample sizes and interpolation policies.

Scope

The core is expected to grow around a small number of benchmarking concepts:

  • precise and replaceable timers;
  • warmup and calibration;
  • iterations and rounds;
  • immutable results and descriptive statistics;
  • reporters and result storage;
  • regression comparison;
  • isolated adapters for testing frameworks.

Features will be designed only when there is a concrete use case. BenchCore will prefer composition and small protocols over speculative abstraction.

Project status

BenchCore is currently pre-alpha. The development version supports explicit rounds, automatic or fixed iterations, warmups, an injectable nanosecond timer, and basic descriptive statistics. Async callables, reporters, storage, framework integrations, and stability guarantees have not been implemented.

No compatibility guarantees apply until an initial public release. Once public APIs exist, changes will be documented in CHANGELOG.md.

Contributing

Early contributions are especially valuable when they clarify use cases and API constraints. Before implementing a substantial feature, please open an issue so the design and trade-offs can be discussed first. See CONTRIBUTING.md for the complete process.

By participating, you agree to follow the Code of Conduct. Please report security issues privately as described in SECURITY.md.

License

BenchCore is distributed under the terms in LICENSE.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

benchcore-0.4.0.tar.gz (7.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

benchcore-0.4.0-py3-none-any.whl (9.7 kB view details)

Uploaded Python 3

File details

Details for the file benchcore-0.4.0.tar.gz.

File metadata

  • Download URL: benchcore-0.4.0.tar.gz
  • Upload date:
  • Size: 7.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for benchcore-0.4.0.tar.gz
Algorithm Hash digest
SHA256 770cf69320fc526ed7e78bc457243edffc60eb1f16a488e16ab5c3d87d530954
MD5 71b5d9cd9bfd696b2a932e7378c31e6f
BLAKE2b-256 1b8cff6d77fb4da3b0f6c581fe77e3c2570c509dff1315259e8d925ea5794508

See more details on using hashes here.

Provenance

The following attestation bundles were made for benchcore-0.4.0.tar.gz:

Publisher: publish.yml on ezer-mackenzie/benchcore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file benchcore-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: benchcore-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 9.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for benchcore-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a5734dd2dcc39adafbb7701eddd5c96f85c68e9b0e96d8f5b62c24eb6ff53a05
MD5 57d7b1187c5265a360a34a8ea6144b00
BLAKE2b-256 93aebb91bf3e980377885b8593eaae3cdf25ea5a7ea017b3a9dac565928473c6

See more details on using hashes here.

Provenance

The following attestation bundles were made for benchcore-0.4.0-py3-none-any.whl:

Publisher: publish.yml on ezer-mackenzie/benchcore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.0.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

This release

0.4.0 This release

2 files

0.3.0

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page