Por que o Rust exige uma abordagem especial para benchmarking?
O Rust é famoso por sua velocidade, mas até a linguagem mais rápida pode ser desacelerada por uma implementação ruim. Você já escreveu um algoritmo em Rust pensando: "Isso vai ser relâmpago!", e depois executou e não entendeu — onde se perderam os microssegundos? Familiar?
Benchmarking em Rust não é apenas iniciar um cronômetro e medir o tempo. É toda uma ciência: é preciso considerar as otimizações do compilador, o cache do processador e até mesmo como o Rust gerencia a memória. Neste artigo, vamos abordar como fazer benchmark de programas em Rust corretamente, para que você obtenha números honestos, e não resultados aleatórios.
Vamos percorrer o caminho desde medições simples com std::time até a ferramenta profissional criterion, usada em projetos reais. Vamos lá!
1. A maneira mais simples: std::time::Instant
Se você precisa "sentir" rapidamente o desempenho de um trecho de código — sem instalar crates, basta inserir a medição diretamente no main. Mas há um problema: o otimizador do Rust pode "descartar" seu código se ele não usar o resultado.
use std::time::Instant;
fn main() { let start = Instant::now();
// Seu código a ser medido let mut sum = 0u64; for i in 0..1_000_000 { sum += i; }
let duration = start.elapsed(); println!("Tempo de execução: {:?}", duration); // Use o resultado obrigatoriamente! println!("Soma: {}", sum);}Por que não posso simplesmente medir uma única execução?
Uma única execução é uma loteria. O sistema pode estar ocupado, o processador pode estar em throttling, o cache pode estar frio. Um benchmark correto em Rust deve:
- Aquecer o código (warm-up) — algumas iterações antes da medição
- Executar o código muitas vezes (milhares de iterações)
- Calcular a média dos resultados
A implementação manual disso é um caminho para erros. Por isso, a comunidade Rust criou o criterion.
2. Benchmarking profissional: a crate Criterion
Criterion.rs é o padrão de facto para benchmarking em Rust. É usado no próprio compilador rustc e em centenas de crates. Por que ele é tão bom?
- Aquecimento automático e tratamento estatístico
- Gráficos e comparação com execuções anteriores
- Detecção de regressões (se o código ficou mais lento — você saberá)
- Suporte a benchmarks parametrizados
Instalação e configuração
Adicione ao Cargo.toml:
[dev-dependencies]criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]name = "my_benchmark"harness = falseCrie o arquivo benches/my_benchmark.rs:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
// Função a ser submetida ao benchmarkfn 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("fibonacci 20", |b| { // black_box — impede o otimizador de descartar o código b.iter(|| fibonacci(black_box(20))) });}
criterion_group!(benches, bench_fibonacci);criterion_main!(benches);Execute: cargo bench. Você verá algo como:
fibonacci 20 time: [24.567 µs 24.789 µs 25.012 µs]Três números representam o intervalo de confiança (limite inferior, médio e superior). Se o intervalo for estreito — o benchmark é estável.
3. O ingrediente secreto: black_box
Você deve ter notado o black_box no exemplo. Esta é uma função mágica do criterion que impede o LLVM de otimizar os argumentos ou o resultado. Sem ela, seu benchmark pode mostrar 0 ns — porque o compilador simplesmente calculou a constante em tempo de compilação.
Exemplo p