benchmark 0.8.0

Nanosecond-precision benchmarking for dev, testing, and production. Zero-overhead core timing when disabled; optional std-powered collectors and zero-dependency metrics (Watch/Timer) for real service observability.
Documentation

Feature Flags

  • none: no features.
  • std (default): uses Rust standard library; disables no_std
  • benchmark (default): enables default benchmark measurement.
  • metrics (optional): production/live metrics (Watch, Timer, stopwatch!).
  • default: convenience feature equal to std + benchmark
  • standard: convenience feature equal to std + benchmark + metrics
  • minimal: minimal build with core timing only (no default features)
  • all: Activates all features (includes: std + benchmark + metrics)

— See FEATURES DOCUMENTATION for more information.

Installation

Add this to your Cargo.toml:

[dependencies]
benchmark = "0.8.0"

Standard Features

Enables all standard benchmark features.

[dependencies]

# Enables Production & Development.
benchmark = { version = "0.8.0", features = ["standard"] }

Production metrics (std + metrics)

Enable production observability using Watch/Timer or the stopwatch! macro.

Cargo features:

[dependencies]
benchmark = { version = "0.8.0", features = ["std", "metrics"] }

Record with Timer (auto-record on drop):

use benchmark::{Watch, Timer};

let watch = Watch::new();
{
    let _t = Timer::new(watch.clone(), "db.query");
    // ... do the work to be measured ...
} // recorded once on drop

let s = &watch.snapshot()["db.query"];
assert!(s.count >= 1);

Or use the stopwatch! macro:

use benchmark::{Watch, stopwatch};

let watch = Watch::new();
stopwatch!(watch, "render", {
    // ... work to measure ...
});
assert!(watch.snapshot()["render"].count >= 1);

Disable Default Features

True zero-overhead core timing only.

[dependencies]
# Disable default features for true zero-overhead
benchmark = { version = "0.8.0", default-features = false }

— See FEATURES DOCUMENTATION for more information.

Quick Start

Measure a closure

Use measure() to time any closure and get back (result, Duration).

use benchmark::measure;

let (value, duration) = measure(|| 2 + 2);
assert_eq!(value, 4);
println!("took {} ns", duration.as_nanos());

Time an expression with the macro

time! works with any expression and supports async contexts.

use benchmark::time;

let (value, duration) = time!({
    let mut sum = 0;
    for i in 0..10_000 { sum += i; }
    sum
});
assert!(duration.as_nanos() > 0);

Micro-benchmark a code block

Use benchmark_block! to run a block many times and get raw per-iteration durations.

use benchmark::benchmark_block;

// Default 10_000 iterations
let samples = benchmark_block!({
    // hot path
    std::hint::black_box(1 + 1);
});
assert_eq!(samples.len(), 10_000);

// Explicit iterations
let samples = benchmark_block!(1_000usize, {
    std::hint::black_box(2 * 3);
});

Macro-benchmark a named expression

Use benchmark! to run a named expression repeatedly and get (last, Vec<Measurement>).

use benchmark::benchmark;

// Default 10_000 iterations
let (last, ms) = benchmark!("add", { 2 + 3 });
assert_eq!(last, Some(5));
assert_eq!(ms[0].name, "add");

// Explicit iterations
let (_last, ms) = benchmark!("mul", 77usize, { 6 * 7 });
assert_eq!(ms.len(), 77);

Named timing + Collector aggregation (std + benchmark)

Record a named measurement and aggregate stats with Collector.

use benchmark::{time_named, Collector};

fn work() {
    std::thread::sleep(std::time::Duration::from_millis(1));
}

let collector = Collector::new();
let (_, m) = time_named!("work", work());
collector.record(&m);

let stats = collector.stats("work").unwrap();
println!(
    "count={} total={}ns min={}ns max={}ns mean={}ns",
    stats.count,
    stats.total.as_nanos(),
    stats.min.as_nanos(),
    stats.max.as_nanos(),
    stats.mean.as_nanos()
);

Async timing with await

The macros inline Instant timing when features = ["std", "benchmark"], so awaiting inside works seamlessly.

use benchmark::time;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let sleep_dur = std::time::Duration::from_millis(10);
    let ((), d) = time!(tokio::time::sleep(sleep_dur).await);
    println!("slept ~{} ms", d.as_millis());
}

Collector Statistics

stats::single/1000 ~ 2.44–2.61 µs stats::single/10000 ~ 26.7–29.1 µs stats::all/k10_n1000 ~ 29.4–33.6 µs stats::all/k50_n1000 ~ 148.6–157.1 µs

Array Baseline (no locks)

stats::array/k1_n10000 ~ 17.15–17.90 µs stats::array/k10_n1000 ~ 15.03–16.25 µs

overhead::measure/measure_closure_add time: [X ns .. Y ns .. Z ns]

overhead::time_macro/time_macro_add time: [X ns .. Y ns .. Z ns]

We welcome contributions! Please see CONTRIBUTING.md for guidelines.