TL;DR: Criterion is a statistics-driven benchmarking library for Rust. It runs your code many times, applies statistical analysis to filter noise, and reports mean execution time with confidence intervals. It generates HTML reports and detects performance regressions between runs. Use it instead of the built-in
#[bench](nightly-only) for reliable, production-quality benchmarks. Add it under[dev-dependencies]and write benchmark functions inbenches/.
How Do You Set Up Criterion?
Add Criterion as a dev-dependency and configure a benchmark target in Cargo.toml.
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "my_benchmark"
harness = falseharness = false disables the built-in test harness so Criterion can control execution. Create benches/my_benchmark.rs:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 {
match n {
0 => 0,
1 => 1,
n => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn bench_fibonacci(c: &mut Criterion) {
c.bench_function("fibonacci 20", |b| {
b.iter(|| fibonacci(black_box(20)))
});
}
criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);Run with cargo bench.
What Is black_box?
black_box prevents the compiler from optimizing away your benchmark code; it is essential for accurate results.
Without black_box, the compiler may detect that the result of fibonacci(20) is constant and precompute it at compile time, making your benchmark measure nothing. black_box(x) is an identity function that hints to the compiler: "treat this as an opaque value."
How Do You Benchmark Throughput?
Use c.throughput() to measure operations per second or bytes per second.
fn bench_parse(c: &mut Criterion) {
let data = b"hello world this is test data";
let mut group = c.benchmark_group("parsing");
group.throughput(criterion::Throughput::Bytes(data.len() as u64));
group.bench_function("parse_bytes", |b| {
b.iter(|| parse(black_box(data)))
});
group.finish();
}Throughput results are reported as GB/s or ops/s; much easier to compare across different input sizes.
How Do You Compare Multiple Implementations?
Use BenchmarkGroup to run multiple functions under the same group for easy comparison.
fn bench_sort(c: &mut Criterion) {
let mut group = c.benchmark_group("sort");
for size in [100, 1_000, 10_000] {
let data: Vec<u64> = (0..size).rev().collect();
group.bench_with_input(format!("std_sort/{size}"), &data, |b, d| {
b.iter(|| { let mut v = d.clone(); v.sort(); v })
});
group.bench_with_input(format!("unstable/{size}"), &data, |b, d| {
b.iter(|| { let mut v = d.clone(); v.sort_unstable(); v })
});
}
group.finish();
}Frequently Asked Questions
Criterion automatically determines iteration count based on how long each iteration takes, targeting a stable sample size. You can override with .sample_size(n) or .measurement_time(duration).
In target/criterion/ after running cargo bench. Open target/criterion/report/index.html in a browser.
Use cargo-criterion (the CLI companion) with --message-format json to emit machine-readable results. Compare against a baseline stored as a file.
Sources
Related Glossary Terms
- cargo: Criterion integrates with Cargo's benchmark system
- tokio: Use
criterionwithtokio-testfor async benchmarks - proptest: Property tests and benchmarks often work together to validate both correctness and performance
- mockall: Benchmarks frequently isolate dependencies the same way mock-based tests do
- Clippy: Clippy and Criterion complement each other by improving code quality before performance tuning
Keep Reading
- Learn Rust in 2026: Performance is one of Rust's core promises; Criterion is how you measure it
