Criterion.rs Documentation

repository·master·Indexed 26 days ago

https://github.com/bheisler/criterion.rs

A statistics-driven micro-benchmarking framework for Rust designed for accurate and reproducible performance measurements. It features a four-phase analysis process (warmup, measurement, analysis, and comparison), outlier classification using Tukey's Method, and support for baseline comparisons. The ecosystem includes the cargo-criterion extension for enhanced configuration via Criterion.toml, the criterion-macro for marking benchmark functions, and criterion-bencher-compat for migrating legacy bencher benchmarks.

Tokens
24.5K
Snippets
52
Records
159
Agent score
90%

What's inside criterion.rs

  1. Visualize performance comparisons with Violin Plots and Line Charts

    master

    When using Criterion::benchmark_group to compare implementations or inputs, Criterion automatically generates visual reports:

    • Violin Plot: Displays the median times and the Probability Density Function (PDF) for each implementation.
    • Line Chart: Shows how different functions perform as the input size increases.
  2. Use the Criterion.rs organization for active development

    master
    Active development of Criterion has moved from the bheisler/bheisler repository to the new criterion-rs organization. For the latest features, bug reports, and contributions, use the official criterion.rs repository.
  3. Use Iai for precise single-shot benchmarking

    master
    Iai is an experimental benchmarking harness that utilizes Cachegrind to perform extremely precise single-shot measurements of Rust code. It is designed to complement Criterion.rs and is particularly useful for achieving reliable benchmarking results within CI (Continuous Integration) environments.
  4. Convert bencher benchmarks to Criterion.rs using criterion-bencher-compat

    master
    The criterion-bencher-compat crate provides a shim designed to facilitate the conversion of existing bencher benchmarks into [Criterion.rs] benchmarks. This allows developers to migrate legacy benchmarking code to the Criterion.rs ecosystem with minimal friction.
  5. Understand the Criterion.rs Analysis Process

    master

    Criterion.rs follows a four-phase process for every benchmark to ensure statistical reliability:

    1. Warmup: Executes the routine repeatedly to populate CPU/OS caches and allow JIT compilation to stabilize. This phase is controlled by the warm_up_time parameter in the Criterion struct.
    2. Measurement: Collects performance data by executing the routine in multiple samples. Each sample contains an increasing number of iterations to meet the measurement_time requirement. Data is stored locally for future comparisons.
    3. Analysis: Distills collected samples into statistics using outlier classification (Tukey's Method) and linear regression via bootstrap sampling to provide confidence intervals.
    4. Comparison: Compares current statistics against data from the previous run using a T-test to detect performance regressions or optimizations.
  6. Benchmark async functions with Criterion.rs

    master

    To benchmark asynchronous functions, you must convert the standard bencher to async mode using the .to_async() method. This method requires a futures executor to run the benchmark. The timing loops function identically to synchronous benchmarks.

    use criterion::BenchmarkId;
    use criterion::Criterion;
    use criterion::{criterion_group, criterion_main};
    
    // Import the desired executor
    use criterion::async_executor::FuturesExecutor;
    
    async fn do_something(size: usize) {
        // Async logic here
    }
    
    fn from_elem(c: &mut Criterion) {
        let size: usize = 1024;
    
        c.bench_with_input(BenchmarkId::new("input_example", size), &size, |b, &s| {
            // Use `to_async` with an executor to benchmark the async function
            b.to_async(FuturesExecutor).iter(|| do_something(s));
        });
    }
    
    criterion_group!(benches, from_elem);
    criterion_main!(benches);
  7. Use custom measurements in benchmarks

    master

    To use a custom measurement, you must:

    1. Provide the measurement to the Criterion instance using .with_measurement(YourMeasurement).
    2. Update your benchmark function signatures to use Criterion<YourMeasurement> instead of the default.
    3. Configure your benchmark group using the custom Criterion instance.

    Note: As of version 0.3.0, only a single measurement can be used per benchmark.

    fn my_benchmark(criterion: &mut Criterion<HalfSeconds>) {
        // Use the criterion struct as normal here.
    }
    
    fn alternate_measurement() -> Criterion<HalfSeconds> {
        Criterion::default().with_measurement(HalfSeconds)
    }
    
    criterion_group! {
        name = benches;
        config = alternate_measurement();
        targets = my_benchmark
    }
  8. Migrate benchmarks from libtest to Criterion.rs

    master

    To migrate an existing libtest or bencher benchmark to Criterion.rs, follow these steps:

    1. Disable the libtest harness: In your Cargo.toml, update the [[bench]] section to set harness = false.
    2. Add Criterion.rs dependency: Add criterion to your [dev-dependencies] in Cargo.toml.
    3. Update Imports: Replace test::Bencher and test::black_box with criterion::{criterion_group, criterion_main, Criterion} and std::hint::black_box respectively.
    4. Refactor Benchmark Functions:
      • Remove the #[bench] attribute.
      • Change the function argument from &mut Bencher to &mut Criterion.
      • Use c.bench_function("name", |b| b.iter(|| ...)) to wrap the benchmark logic.
    5. Generate Main Function: Use the criterion_group! and criterion_main! macros to generate the necessary entry point for the benchmark executable.
    use criterion::{criterion_group, criterion_main, Criterion};
    use std::hint::black_box;
    
    fn fibonacci(n: u64) -> u64 {
        match n {
            0 => 1,
            1 => 1,
            n => fibonacci(n-1) + fibonacci(n-2),
        }
    }
    
    fn bench_fib(c: &mut Criterion) {
        c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
    }
    
    criterion_group!(benches, bench_fib);
    criterion_main!(benches);
  9. Configure Chart Axis Scaling

    master

    When using parameterized benchmarks where input sizes scale exponentially, you can switch from the default linear axis to a logarithmic axis in generated plots. This is done by configuring a PlotConfiguration with AxisScale::Logarithmic and applying it to a BenchmarkGroup via .plot_config().

    use criterion::*;
    
    fn do_a_thing(x: u64) {}
    
    fn bench(c: &mut Criterion) {
        let plot_config = PlotConfiguration::default()
            .summary_scale(AxisScale::Logarithmic);
    
        let mut group = c.benchmark_group("log_scale_example");
        group.plot_config(plot_config);
        
        for i in [1u64, 10u64, 100u64, 1000u64].iter() {
            group.bench_function(BenchmarkId::from_parameter(i), i, |b, i| b.iter(|| do_a_thing(i)));
        }
        group.finish();
    }
    
    criterion_group!(benches, bench);
    criterion_main!(benches);
  10. Benchmark a part of a function

    master

    Criterion.rs cannot accurately measure only a portion of a function due to the granularity of the system clock.

    Recommended Approach: Extract the specific logic into a new function, name it, and benchmark that function. You can use #[inline(always)] to ensure the compiler inlines it back into the original callsite in the final executable.

    Advanced/Less Accurate Approach: If you accept reduced accuracy, you can use Bencher::iter_custom to implement your own timing loop. This is useful for complex cases like multi-threaded code, but you are responsible for the measurement accuracy.