Loading pageā¦
Rust walkthroughs
Loading pageā¦
criterion::BatchSize control sample sizes for statistically significant benchmarking?criterion::BatchSize controls how many iterations of a benchmark run in a single sample, determining the granularity of measurement and the statistical reliability of resultsāsmaller batch sizes provide finer-grained timing data but may suffer from measurement noise, while larger batch sizes amortize per-iteration overhead but can obscure per-iteration performance characteristics. The BatchSize parameter directly influences how Criterion collects samples: it runs the benchmark function batch_size times per sample, measures the total elapsed time, and divides by batch_size to compute per-iteration time, allowing Criterion to measure extremely fast operations that would otherwise fall below timer resolution and to build statistically robust estimates through repeated sampling.
use criterion::{Criterion, black_box};
fn main() {
let mut c = Criterion::default();
// Criterion auto-detects batch size by default
// It runs iterations until timing data is statistically stable
c.bench_function("default_batch", |b| {
b.iter(|| {
// Simple computation
(0..1000).fold(0, |acc, x| acc + x)
})
});
c.final_summary();
}By default, Criterion automatically determines batch size based on estimated iteration time.
use criterion::{Criterion, BatchSize, black_box};
fn main() {
let mut c = Criterion::default();
// Small batch size: more samples, more overhead per sample
c.bench_function("small_batch", |b| {
b.iter_batched(
|| Vec::<u8>::with_capacity(1024),
|v| {
// Setup runs once, this closure runs batch_size times
v.len()
},
BatchSize::Small, // ~10-100 iterations per sample
)
});
c.final_summary();
}BatchSize::Small uses approximately 10-100 iterations per sample, suitable for slower operations.
use criterion::{Criterion, BatchSize, black_box};
fn main() {
let mut c = Criterion::default();
// Large batch size: fewer samples, less overhead, more iterations
c.bench_function("large_batch", |b| {
b.iter_batched(
|| 0u64,
|n| n.wrapping_add(1),
BatchSize::Large, // ~10,000+ iterations per sample
)
});
c.final_summary();
}BatchSize::Large uses 10,000 or more iterations per sample for very fast operations.
use criterion::{Criterion, BatchSize, black_box};
fn fast_operation() -> u64 {
// Extremely fast - may be sub-nanosecond
black_box(42)
}
fn main() {
let mut c = Criterion::default();
// Without batch size, fast operations are hard to measure
// Timer resolution is typically ~100ns
// Large batch amortizes timing overhead
c.bench_function("fast_with_large_batch", |b| {
b.iter_batched(
|| (),
|_| fast_operation(),
BatchSize::Large,
)
});
// Custom exact batch size for precise control
c.bench_function("fast_exact_batch", |b| {
b.iter_batched(
|| (),
|_| fast_operation(),
BatchSize::PerIteration, // Exactly one iteration
)
});
c.final_summary();
}Large batches help measure operations faster than timer resolution.
use criterion::{Criterion, BatchSize, black_box};
use std::collections::HashMap;
fn main() {
let mut c = Criterion::default();
// Setup cost is excluded from timing
c.bench_function("hashmap_lookup", |b| {
b.iter_batched(
|| {
// Setup: runs once per batch
let mut map = HashMap::new();
for i in 0..1000 {
map.insert(i, i * 2);
}
(map, 500) // Return both map and lookup key
},
|(map, key)| {
// Routine: runs batch_size times
map.get(&key)
},
BatchSize::Small, // Each sample does ~100 lookups
)
});
c.final_summary();
}iter_batched isolates setup cost; only the routine is timed.
use criterion::{Criterion, BatchSize, black_box};
use std::collections::VecDeque;
fn main() {
let mut c = Criterion::default();
// Setup creates fresh state for each batch
c.bench_function("vecdeque_push", |b| {
b.iter_batched_ref(
|| VecDeque::with_capacity(1000),
|deque| {
// Routine can mutate the deque
deque.push_back(42);
deque.len()
},
BatchSize::Small, // Batches of ~100 pushes
)
});
// Compare with iter_batched (immutable reference)
c.bench_function("vecdeque_peek", |b| {
b.iter_batched(
|| {
let mut deque = VecDeque::new();
deque.push_back(42);
deque
},
|deque| {
deque.front()
},
BatchSize::Small,
)
});
c.final_summary();
}iter_batched_ref provides mutable references for each iteration in the batch.
use criterion::{Criterion, BatchSize, black_box};
fn main() {
let mut c = Criterion::default();
// Exact number of iterations per sample
c.bench_function("exact_iterations", |b| {
b.iter_batched(
|| 0u64,
|n| n.wrapping_add(1),
BatchSize::NumIterations(1000), // Exactly 1000 iterations
)
});
// Very large for extremely fast operations
c.bench_function("very_fast", |b| {
b.iter_batched(
|| (),
|_| black_box(1 + 1),
BatchSize::NumIterations(1_000_000), // 1 million iterations
)
});
c.final_summary();
}BatchSize::NumIterations(n) specifies exactly n iterations per sample.
use criterion::{Criterion, BatchSize};
fn main() {
let mut c = Criterion::default()
.sample_size(100); // Number of samples
// With sample_size=100 and BatchSize::Small (~100 iterations):
// Total iterations ā 100 * 100 = 10,000
c.bench_function("with_sample_size", |b| {
b.iter_batched(
|| 0u64,
|n| n + 1,
BatchSize::Small,
)
});
// More samples = better statistical confidence
// But also longer benchmark time
let c2 = Criterion::default()
.sample_size(1000); // 10x more samples
c2.bench_function("more_samples", |b| {
b.iter_batched(
|| 0u64,
|n| n + 1,
BatchSize::Small,
)
});
}Sample count affects statistical confidence; batch size affects per-sample iteration count.
use criterion::{Criterion, BatchSize, black_box};
use std::time::Instant;
fn main() {
// Timer resolution is typically limited
// On most systems: ~100ns - 1μs
let mut c = Criterion::default();
// Operation taking ~1ns per iteration
c.bench_function("nanosecond_op", |b| {
b.iter_batched(
|| (),
|_| {
// This is too fast to measure directly
black_box(1u64.wrapping_add(1))
},
BatchSize::NumIterations(10_000), // Need 10,000 iterations
// 10,000 * 1ns = 10μs, measurable above timer resolution
)
});
// Operation taking ~1μs per iteration
c.bench_function("microsecond_op", |b| {
b.iter_batched(
|| (),
|_| {
let v: Vec<u8> = (0..100).collect();
v.len()
},
BatchSize::Small, // ~100 iterations sufficient
// 100 * 1μs = 100μs, easily measurable
)
});
c.final_summary();
}Fast operations need larger batch sizes to exceed timer resolution.
use criterion::{Criterion, BatchSize, black_box};
fn main() {
let mut c = Criterion::default();
// Allocate with setup to measure just the allocation
c.bench_function("allocate_1kb", |b| {
b.iter_batched(
|| (),
|_| Vec::<u8>::with_capacity(1024),
BatchSize::Small, // ~100 allocations per sample
)
});
// For allocations, want enough iterations for stable measurement
// But not so many that allocator overhead accumulates
c.bench_function("allocate_1mb", |b| {
b.iter_batched(
|| (),
|_| Vec::<u8>::with_capacity(1024 * 1024),
BatchSize::PerIteration, // One at a time for large allocations
)
});
c.final_summary();
}Batch size affects memory allocator behavior; smaller batches for large allocations.
use criterion::{Criterion, BatchSize, black_box};
fn fibonacci(n: u64) -> u64 {
if n <= 1 {
n
} else {
fibonacci(n - 1) + fibonacci(n - 2)
}
}
fn main() {
let mut c = Criterion::default();
// Small batch for slow operation
c.bench_function("fib_slow_small", |b| {
b.iter_batched(
|| 20u64,
|n| fibonacci(n),
BatchSize::Small, // fibonacci(20) is slow enough
)
});
// PerIteration for very slow operations
c.bench_function("fib_slow_single", |b| {
b.iter_batched(
|| 25u64,
|n| fibonacci(n),
BatchSize::PerIteration, // One iteration per sample
)
});
// For fast operations, larger batches needed
c.bench_function("fib_fast_large", |b| {
b.iter_batched(
|| 10u64,
|n| fibonacci(n),
BatchSize::Large, // fibonacci(10) is very fast
)
});
c.final_summary();
}Match batch size to operation speed: slower operations need smaller batches.
use criterion::{Criterion, BatchSize};
fn main() {
let mut c = Criterion::default()
.warm_up_time(std::time::Duration::from_secs(1))
.measurement_time(std::time::Duration::from_secs(3));
// Warm-up runs iterations to stabilize CPU caches, JIT, etc.
// Batch size affects how iterations are grouped during warm-up too
c.bench_function("with_warmup", |b| {
b.iter_batched(
|| Vec::<u64>::with_capacity(1000),
|v| v.len(),
BatchSize::Small,
)
});
// After warm-up, measurement phase collects samples
// With sample_size=100 and BatchSize::Small(~100):
// ~10,000 iterations during measurement
c.final_summary();
}Warm-up runs before measurement; batch size affects warm-up iteration grouping.
use criterion::{Criterion, BatchSize, Throughput};
fn main() {
let mut c = Criterion::default();
// Throughput requires knowing how much work per iteration
c.bench_function("process_bytes", |b| {
b.throughput(Throughput::Bytes(1024)) // 1KB per iteration
.iter_batched(
|| vec
![0u8; 1024],
|data| {
data.iter().sum::<u8>()
},
BatchSize::Small,
)
});
// Throughput + batch size: Criterion knows total bytes processed
// Can report bytes/second in addition to time/iteration
c.bench_function("process_items", |b| {
b.throughput(Throughput::Elements(100)) // 100 items per iteration
.iter_batched(
|| (0..100).collect::<Vec<_>>(),
|items| items.len(),
BatchSize::Small,
)
});
c.final_summary();
}Throughput metrics combined with batch size give meaningful throughput measurements.
use criterion::{Criterion, BatchSize, black_box};
fn main() {
let mut c = Criterion::default();
// MISTAKE 1: Too small batch for fast operation
// c.bench_function("too_small", |b| {
// b.iter_batched(
// || (),
// |_| black_box(1 + 1),
// BatchSize::PerIteration, // Timer can't measure ~1ns
// )
// });
// FIX: Use larger batch for fast operations
c.bench_function("correct_batch", |b| {
b.iter_batched(
|| (),
|_| black_box(1 + 1),
BatchSize::NumIterations(100_000),
)
});
// MISTAKE 2: Setup cost included in timing (using iter instead of iter_batched)
// c.bench_function("setup_included", |b| {
// b.iter(|| {
// let mut v = Vec::new(); // Setup inside iter!
// for i in 0..1000 { v.push(i); }
// v.len()
// })
// });
// FIX: Use iter_batched to exclude setup
c.bench_function("setup_excluded", |b| {
b.iter_batched(
|| (0..1000).collect::<Vec<_>>(),
|v| v.len(),
BatchSize::Small,
)
});
c.final_summary();
}Common pitfalls: batch too small for operation speed, setup inside timed region.
use criterion::BatchSize;
fn main() {
// PerIteration: exactly 1 iteration per sample
let _per_iter = BatchSize::PerIteration;
// Use for: very slow operations (>1ms), large allocations
// Small: ~10-100 iterations per sample
let _small = BatchSize::Small;
// Use for: moderate operations (>1μs), typical code
// Large: ~10,000+ iterations per sample
let _large = BatchSize::Large;
// Use for: very fast operations (<100ns), simple arithmetic
// NumIterations(n): exactly n iterations per sample
let _exact = BatchSize::NumIterations(5000);
// Use for: precise control, known iteration counts
}Choose batch size based on operation duration and timer resolution.
BatchSize variants:
| Variant | Iterations | Use Case |
|---------|------------|----------|
| PerIteration | 1 | Very slow operations (>1ms) |
| Small | ~10-100 | Moderate operations (>1μs) |
| Large | ~10,000+ | Very fast operations (<100ns) |
| NumIterations(n) | exactly n | Precise control |
Batch size effects:
| Batch Size | Samples | Per-Iteration Noise | Total Time | |------------|---------|--------------------|------------| | Small | More samples | Higher noise | Lower | | Large | Fewer samples | Lower noise | Higher | | Auto | Criterion decides | Balanced | Balanced |
Key insight: criterion::BatchSize controls the fundamental trade-off in benchmarking between statistical reliability and measurement accuracy. Each sample runs the benchmark batch_size times, and Criterion measures the total time then divides by iteration countāthis allows measuring operations faster than timer resolution (sub-nanosecond operations need thousands of iterations to accumulate measurable time) and amortizing per-sample overhead (timing function calls, loop overhead). The choice depends on operation speed: PerIteration for slow operations where each iteration is measurable, Small for typical operations where ~100 iterations gives stable measurements, Large for very fast operations that need many iterations to exceed timer resolution, and NumIterations for exact control. Combined with iter_batched and iter_batched_ref, batch size enables isolating setup costs from measured routine executionāsetup runs once per batch, routine runs batch_size times per sample, and only routine time is measured. Use smaller batches when setup is expensive or you want more statistical samples; use larger batches when the operation is very fast and you need accumulated time above timer resolution.