Loading page…
Rust walkthroughs
Loading page…
criterion::BenchmarkGroup::bench_function isolate benchmarks from external noise?criterion::BenchmarkGroup::bench_function isolates benchmarks from external noise through statistical analysis, warm-up iterations, and measurement techniques that account for system variability. The benchmark framework runs multiple iterations, collects timing samples, applies statistical methods to detect outliers and estimate true performance, and reports confidence intervals rather than single-point measurements. This approach distinguishes genuine performance differences from random noise caused by CPU frequency scaling, cache state variations, OS scheduling, memory allocation patterns, and other system-level perturbations.
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn bench_fibonacci(c: &mut Criterion) {
c.bench_function("fib 20", |b| {
b.iter(|| fibonacci(black_box(20)));
});
}
criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);bench_function accepts a closure that receives a Bencher for running iterations.
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn expensive_computation(n: usize) -> usize {
(0..n).sum()
}
fn bench_with_warmup(c: &mut Criterion) {
// Criterion automatically performs warm-up before measurement
// This ensures caches are hot and JIT compilation is complete
c.bench_function("sum 1000", |b| {
b.iter(|| expensive_computation(black_box(1000)));
});
}
criterion_group!(benches, bench_with_warmup);
criterion_main!(benches);Warm-up runs many iterations before measuring, filling caches and triggering JIT compilation.
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn compute(x: u64) -> u64 {
x * x + x
}
fn bench_without_black_box(c: &mut Criterion) {
// DANGEROUS: Compiler might optimize away the computation
c.bench_function("compute_naive", |b| {
b.iter(|| compute(42));
});
}
fn bench_with_black_box(c: &mut Criterion) {
// CORRECT: black_box prevents optimization
c.bench_function("compute_correct", |b| {
b.iter(|| compute(black_box(42)));
});
}
criterion_group!(benches, bench_without_black_box, bench_with_black_box);
criterion_main!(benches);black_box prevents the compiler from optimizing away computations by hiding values from the optimizer.
use criterion::{criterion_group, criterion_main, Criterion};
fn vector_push(n: usize) -> Vec<u64> {
let mut v = Vec::with_capacity(n);
for i in 0..n {
v.push(i as u64);
}
v
}
fn bench_statistics(c: &mut Criterion) {
c.bench_function("vector_push", |b| {
b.iter(|| vector_push(100));
});
// Criterion automatically:
// 1. Runs multiple iterations
// 2. Collects timing samples
// 3. Calculates statistics
// 4. Reports confidence intervals
// 5. Detects outliers
}
criterion_group!(benches, bench_statistics);
criterion_main!(benches);Multiple samples are collected and analyzed statistically, not just timed once.
use criterion::{criterion_group, criterion_main, Criterion, BatchSize};
fn allocation_heavy(n: usize) -> Vec<u8> {
vec
![0u8; n]
}
fn bench_batch_size(c: &mut Criterion) {
// Criterion automatically determines iteration count
// based on how long each iteration takes
// For fast functions, many iterations per sample
// For slow functions, fewer iterations per sample
c.bench_function("allocate", |b| {
b.iter(|| allocation_heavy(1024));
});
// You can also specify batch size manually:
c.bench_function("allocate_batch", |b| {
b.iter_batched(
|| 1024, // Setup
|n| allocation_heavy(n), // Routine
BatchSize::SmallInput, // Batch size
);
});
}
criterion_group!(benches, bench_batch_size);
criterion_main!(benches);Iteration counts are automatically calibrated based on function duration.
use criterion::{criterion_group, criterion_main, Criterion};
fn noisy_operation() -> u64 {
// Simulate an operation with variable runtime
std::hint::black_box(42)
}
fn bench_outliers(c: &mut Criterion) {
c.bench_function("noisy_op", |b| {
b.iter(|| noisy_operation());
});
// Criterion output includes:
// - Outlier detection
// - Confidence intervals
// - Standard deviation
// - Median vs mean comparison
}
criterion_group!(benches, bench_outliers);
criterion_main!(benches);Outliers are identified and reported separately from main statistics.
use criterion::{criterion_group, criterion_main, Criterion};
fn process_data(data: &[u8]) -> u64 {
data.iter().map(|&b| b as u64).sum()
}
fn bench_confidence(c: &mut Criterion) {
let data = vec
![0u8; 10000];
c.bench_function("process_data", |b| {
b.iter(|| process_data(&data));
});
// Criterion reports:
// - Mean time with confidence interval
// - Lower/upper bounds (95% confidence)
// - Helps distinguish real differences from noise
}
criterion_group!(benches, bench_confidence);
criterion_main!(benches);Confidence intervals show the range where true performance likely falls.
use criterion::{criterion_group, criterion_main, Criterion};
fn linear_search(data: &[i32], target: i32) -> Option<usize> {
data.iter().position(|&x| x == target)
}
fn bench_regression(c: &mut Criterion) {
let data: Vec<i32> = (0..1000).collect();
c.bench_function("linear_search", |b| {
b.iter(|| {
let target = black_box(500);
linear_search(&data, target)
});
});
// Criterion uses linear regression to estimate
// per-iteration time from multiple samples
}
criterion_group!(benches, bench_regression);
criterion_main!(benches);Linear regression separates iteration time from measurement overhead.
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
fn copy_bytes(src: &[u8], dst: &mut [u8]) {
dst.copy_from_slice(src);
}
fn bench_throughput(c: &mut Criterion) {
let size = 1024 * 1024; // 1MB
let src = vec
![0u8; size];
let mut dst = vec
![0u8; size];
c.bench_function("copy_1mb", |b| {
b.throughput(Throughput::Bytes(size as u64));
b.iter(|| copy_bytes(&src, &mut dst));
});
// Reports throughput (MB/s) instead of just time
// Useful for I/O benchmarks
}
criterion_group!(benches, bench_throughput);
criterion_main!(benches);Throughput converts timing results into bytes/elements per second.
use criterion::{criterion_group, criterion_main, Criterion, BatchSize};
fn sum_iter(data: &[u64]) -> u64 {
data.iter().sum()
}
fn sum_loop(data: &[u64]) -> u64 {
let mut total = 0;
for &x in data {
total += x;
}
total
}
fn bench_comparison(c: &mut Criterion) {
let data: Vec<u64> = (0..10000).collect();
let mut group = c.benchmark_group("sum_methods");
group.bench_function("iter", |b| {
b.iter(|| sum_iter(black_box(&data)));
});
group.bench_function("loop", |b| {
b.iter(|| sum_loop(black_box(&data)));
});
group.finish();
// Comparison shows relative performance
// Criterion highlights significant differences
}
criterion_group!(benches, bench_comparison);
criterion_main!(benches);Benchmark groups enable direct comparison between implementations.
use criterion::{criterion_group, criterion_main, Criterion, BatchSize};
fn sort_vector(v: &mut Vec<i32>) {
v.sort();
}
fn bench_batched(c: &mut Criterion) {
c.bench_function("sort", |b| {
b.iter_batched(
|| (0..1000).rev().collect::<Vec<i32>>(), // Setup: create new vector each time
|mut v| {
sort_vector(&mut v);
v
},
BatchSize::SmallInput,
);
});
// iter_batched separates setup from measured code
// Setup is not included in timing
}
criterion_group!(benches, bench_batched);
criterion_main!(benches);iter_batched runs setup before each measured iteration, excluding it from timing.
use criterion::{criterion_group, criterion_main, Criterion};
fn optimized_function(n: usize) -> usize {
(0..n).filter(|x| x % 2 == 0).sum()
}
fn bench_regression_detection(c: &mut Criterion) {
c.bench_function("optimized", |b| {
b.iter(|| optimized_function(black_box(1000)));
});
// Criterion saves baseline measurements
// Subsequent runs compare against baseline
// Detects performance regressions automatically
}
criterion_group!(benches, bench_regression_detection);
criterion_main!(benches);
// Run with: cargo bench -- --save-baseline main
// Compare with: cargo bench -- --baseline mainBaselines enable detecting performance regressions across code changes.
use criterion::{criterion_group, criterion_main, Criterion};
fn sample_count_demo(c: &mut Criterion) {
// Criterion uses multiple samples per benchmark
// Each sample contains multiple iterations
// Samples are spread across the run
// Default: 100 samples
// Can be configured:
let mut group = c.benchmark_group("configured");
// Set custom sample size and warm-up time
group.sample_size(50) // Number of samples
.measurement_time(std::time::Duration::from_secs(5))
.warm_up_time(std::time::Duration::from_millis(100));
group.bench_function("custom_sampled", |b| {
b.iter(|| {
// Some computation
(0..1000).sum::<u64>()
});
});
group.finish();
}
criterion_group!(benches, sample_count_demo);
criterion_main!(benches);Sample count and timing can be configured for specific measurement needs.
use criterion::{criterion_group, criterion_main, Criterion};
// Sources of noise that Criterion mitigates:
// 1. CPU frequency scaling (warm-up stabilizes)
// 2. Cache state (repeated iterations)
// 3. Memory allocator variability (batch size)
// 4. OS scheduling (statistical analysis)
// 5. Thermal throttling (confidence intervals)
fn memory_intensive(n: usize) -> Vec<u64> {
(0..n).collect()
}
fn bench_noise_handling(c: &mut Criterion) {
// Criterion handles noise through:
// - Warm-up iterations
// - Multiple samples
// - Statistical analysis
// - Outlier detection
c.bench_function("memory_ops", |b| {
b.iter(|| memory_intensive(black_box(10000)));
});
}
criterion_group!(benches, bench_noise_handling);
criterion_main!(benches);Multiple noise sources are handled through warm-up, sampling, and statistics.
use criterion::{criterion_group, criterion_main, Criterion};
use tokio::runtime::Runtime;
async fn async_compute(n: u64) -> u64 {
let mut sum = 0;
for i in 0..n {
sum += i;
}
sum
}
fn bench_async(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
c.bench_function("async_compute", |b| {
b.to_async(&rt).iter(|| async_compute(black_box(1000)));
});
// to_async handles async benchmarks
// Uses Tokio runtime for execution
}
criterion_group!(benches, bench_async);
criterion_main!(benches);to_async enables benchmarking async functions with a runtime.
use criterion::{criterion_group, criterion_main, Criterion, Profiler};
// Custom profiler can be integrated for detailed analysis
struct MyProfiler;
impl Profiler for MyProfiler {
fn start_profiling(&mut self, benchmark_id: &str, benchmark_path: &std::path::Path) {
// Start profiling tool
}
fn stop_profiling(&mut self, benchmark_id: &str, benchmark_path: &std::path::Path) {
// Stop profiling tool
}
}
fn bench_with_profiler(c: &mut Criterion) {
// Profiler integration available for advanced analysis
c.bench_function("profiled", |b| {
b.iter(|| (0..1000).sum::<u64>());
});
}
criterion_group!(benches, bench_with_profiler);
criterion_main!(benches);Profiler integration enables detailed analysis with external tools.
use criterion::{black_box, criterion_group, criterion_main, Criterion, BatchSize};
fn process_input(input: &[u8]) -> u64 {
input.iter().map(|&b| b as u64).sum()
}
fn bench_input_generation(c: &mut Criterion) {
// Input generation excluded from timing
c.bench_function("process_input", |b| {
b.iter_batched(
|| (0..1000).map(|i| i as u8).collect::<Vec<u8>>(),
|input| process_input(&input),
BatchSize::SmallInput,
);
});
// Alternative: pre-generate input once
let data: Vec<u8> = (0..1000).map(|i| i as u8).collect();
c.bench_function("process_input_cached", |b| {
b.iter(|| process_input(black_box(&data)));
});
}
criterion_group!(benches, bench_input_generation);
criterion_main!(benches);Pre-generate inputs or use iter_batched to exclude input generation from timing.
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
fn binary_search(data: &[u32], target: u32) -> Option<usize> {
data.binary_search(&target).ok()
}
fn bench_parameterized(c: &mut Criterion) {
let mut group = c.benchmark_group("binary_search");
let sizes = [100, 1000, 10000, 100000];
for size in sizes {
let data: Vec<u32> = (0..size).collect();
let target = size / 2;
group.bench_with_input(
BenchmarkId::new("size", size),
&(&data, target),
|b, &(data, target)| {
b.iter(|| binary_search(black_box(data), black_box(target)));
},
);
}
group.finish();
// Produces separate results for each parameter value
}
criterion_group!(benches, bench_parameterized);
criterion_main!(benches);BenchmarkId creates parameterized benchmarks for comparing across input sizes.
Noise isolation techniques:
| Technique | Purpose | |-----------|---------| | Warm-up iterations | Fill caches, trigger JIT, stabilize CPU frequency | | Multiple samples | Collect statistical distribution | | Confidence intervals | Show true performance range | | Outlier detection | Identify and exclude anomalies | | Linear regression | Separate iteration time from overhead |
Benchmark phases:
| Phase | Activity | |-------|----------| | Warm-up | Many iterations, not timed | | Measurement | Timed samples collected | | Analysis | Statistics calculated | | Reporting | Results formatted |
Key methods:
| Method | Use case |
|--------|----------|
| bench_function | Simple benchmarks |
| bench_with_input | Parameterized benchmarks |
| iter | Basic iteration timing |
| iter_batched | Setup/teardown separation |
| to_async | Async function benchmarking |
Output interpretation:
| Metric | Meaning | |--------|---------| | Mean | Average iteration time | | Median | Middle value, robust to outliers | | Std dev | Variability measure | | Confidence interval | True value range (95%) | | Outliers | Extreme values detected | | Slope | Per-iteration time from regression |
Best practices:
// Prevent optimization
b.iter(|| compute(black_box(input)));
// Separate setup
b.iter_batched(setup, routine, BatchSize::SmallInput);
// Group related benchmarks
let mut group = c.benchmark_group("name");
group.bench_function("a", ...);
group.bench_function("b", ...);
group.finish();
// Report throughput for I/O
b.throughput(Throughput::Bytes(n));
b.iter(|| read_bytes());Key insight: bench_function provides statistically rigorous benchmarking through a multi-phase approach: warm-up iterations stabilize the execution environment (filling caches, triggering JIT compilation, allowing CPU frequency to settle), measurement collects multiple samples with multiple iterations each, and statistical analysis computes confidence intervals that distinguish genuine performance differences from noise. The framework uses linear regression on sample data to estimate per-iteration time, automatically calibrates iteration counts based on function duration (fast functions get more iterations, slow functions get fewer), and reports both point estimates and uncertainty ranges. black_box prevents compiler optimizations from eliminating benchmarked code, while iter_batched separates setup from measured code. The resulting confidence intervals and outlier detection help developers distinguish real performance changes from measurement noise, making it possible to identify even small (5-10%) performance differences reliably across runs.