Medir o tempo de execução é essencial para otimizar performance. Rust oferece std::time::Instant na biblioteca padrão para medições simples e a crate criterion para benchmarks estatisticamente rigorosos. Nesta receita, você vai aprender ambas as abordagens.
Dependências
Para medições simples, não precisa de crates extras. Para benchmarks com criterion:
[package]
name = "receita-benchmark"
version = "0.1.0"
edition = "2021"
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "meu_benchmark"
harness = false
Código Completo (Medição Simples)
use std::time::{Duration, Instant};
use std::hint::black_box;
fn main() {
// =============================================
// 1. Instant::now() + elapsed() — medição básica
// =============================================
let inicio = Instant::now();
// Código a ser medido
let soma: u64 = (1..=1_000_000).sum();
let duracao = inicio.elapsed();
println!("=== Medição Básica ===");
println!("Soma de 1 a 1.000.000 = {}", soma);
println!("Tempo: {:?}", duracao);
println!("Tempo em microssegundos: {}μs", duracao.as_micros());
println!("Tempo em milissegundos: {}ms", duracao.as_millis());
// =============================================
// 2. Função auxiliar para medir tempo
// =============================================
fn medir<F, R>(nome: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let inicio = Instant::now();
let resultado = f();
let duracao = inicio.elapsed();
println!("{}: {:?}", nome, duracao);
resultado
}
println!("\n=== Função medir() ===");
medir("Vec push 100k", || {
let mut v = Vec::new();
for i in 0..100_000 {
v.push(i);
}
v
});
medir("Vec com capacidade 100k", || {
let mut v = Vec::with_capacity(100_000);
for i in 0..100_000 {
v.push(i);
}
v
});
// =============================================
// 3. Comparando algoritmos
// =============================================
println!("\n=== Comparação de Algoritmos ===");
// Gerar dados de teste
let mut dados: Vec<i32> = (0..50_000).rev().collect();
// sort() — TimSort, estável
let mut dados_sort = dados.clone();
medir("sort() estável", || {
dados_sort.sort();
});
// sort_unstable() — Pattern-defeating quicksort
let mut dados_unstable = dados.clone();
medir("sort_unstable()", || {
dados_unstable.sort_unstable();
});
// Verificar se são iguais
assert_eq!(dados_sort, dados_unstable);
// =============================================
// 4. Medindo múltiplas iterações
// =============================================
println!("\n=== Múltiplas Iterações ===");
fn benchmark_simples<F>(nome: &str, iteracoes: u32, mut f: F)
where
F: FnMut(),
{
let inicio = Instant::now();
for _ in 0..iteracoes {
f();
}
let total = inicio.elapsed();
let media = total / iteracoes;
println!(
"{}: total {:?} | média {:?} ({} iterações)",
nome, total, media, iteracoes
);
}
benchmark_simples("String concatenação", 10_000, || {
let mut s = String::new();
for i in 0..100 {
s.push_str(&i.to_string());
}
black_box(s);
});
benchmark_simples("String com format!", 10_000, || {
let mut s = String::new();
for i in 0..100 {
s = format!("{}{}", s, i);
}
black_box(s);
});
benchmark_simples("String com capacidade", 10_000, || {
let mut s = String::with_capacity(256);
for i in 0..100 {
s.push_str(&i.to_string());
}
black_box(s);
});
// =============================================
// 5. Medindo operações com HashMap vs BTreeMap
// =============================================
println!("\n=== HashMap vs BTreeMap ===");
use std::collections::{HashMap, BTreeMap};
let n = 100_000;
medir("HashMap insert 100k", || {
let mut map = HashMap::new();
for i in 0..n {
map.insert(i, i * 2);
}
black_box(map);
});
medir("BTreeMap insert 100k", || {
let mut map = BTreeMap::new();
for i in 0..n {
map.insert(i, i * 2);
}
black_box(map);
});
// =============================================
// 6. black_box — evitar otimizações do compilador
// =============================================
println!("\n=== Importância do black_box ===");
println!("Use std::hint::black_box() para impedir que o");
println!("compilador otimize código que não tem efeito colateral.");
println!("Sem black_box, o compilador pode eliminar o código inteiro!");
let inicio = Instant::now();
let resultado = black_box((1..=1_000_000).sum::<u64>());
println!("Com black_box: {:?} (resultado: {})", inicio.elapsed(), resultado);
// =============================================
// 7. Timeout e deadline
// =============================================
println!("\n=== Deadline ===");
let deadline = Instant::now() + Duration::from_millis(100);
let mut contador = 0u64;
while Instant::now() < deadline {
contador += 1;
black_box(contador);
}
println!("Iterações em 100ms: {}", contador);
}
Exemplo de Benchmark com Criterion
Crie o arquivo benches/meu_benchmark.rs:
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 fibonacci_iterativo(n: u64) -> u64 {
if n <= 1 {
return n;
}
let (mut a, mut b) = (0u64, 1u64);
for _ in 2..=n {
let temp = a + b;
a = b;
b = temp;
}
b
}
fn benchmark_fibonacci(c: &mut Criterion) {
c.bench_function("fibonacci recursivo 20", |b| {
b.iter(|| fibonacci(black_box(20)))
});
c.bench_function("fibonacci iterativo 20", |b| {
b.iter(|| fibonacci_iterativo(black_box(20)))
});
}
criterion_group!(benches, benchmark_fibonacci);
criterion_main!(benches);
Execute o benchmark com:
cargo bench
Saída do Programa (Medição Simples)
=== Medição Básica ===
Soma de 1 a 1.000.000 = 500000500000
Tempo: 1.234ms
Tempo em microssegundos: 1234μs
Tempo em milissegundos: 1ms
=== Função medir() ===
Vec push 100k: 2.456ms
Vec com capacidade 100k: 1.123ms
=== Comparação de Algoritmos ===
sort() estável: 5.678ms
sort_unstable(): 3.456ms
=== Múltiplas Iterações ===
String concatenação: total 89.123ms | média 8.912μs (10000 iterações)
String com format!: total 234.567ms | média 23.456μs (10000 iterações)
String com capacidade: total 67.890ms | média 6.789μs (10000 iterações)
=== HashMap vs BTreeMap ===
HashMap insert 100k: 12.345ms
BTreeMap insert 100k: 18.678ms
=== Importância do black_box ===
Use std::hint::black_box() para impedir que o
compilador otimize código que não tem efeito colateral.
Sem black_box, o compilador pode eliminar o código inteiro!
Com black_box: 1.234ms (resultado: 500000500000)
=== Deadline ===
Iterações em 100ms: 234567890
Dicas de Performance
- Use
black_box()para evitar que o compilador elimine código “morto” em benchmarks. sort_unstable()geralmente é mais rápido quesort()por usar menos memória.- Pre-aloque com
with_capacity()quando souber o tamanho aproximado. push_str()é mais eficiente queformat!()para construir strings.- Use
criterionpara benchmarks sérios — ele calcula estatísticas, detecta outliers e gera relatórios HTML.
Veja Também
- Trabalhar com Datas em Rust — use chrono para datas e horários completos
- Ordenar Vetor em Rust — compare sort() vs sort_unstable()
- Tutorial: Testes em Rust — escreva testes unitários e de integração
- Rust Cheatsheet — referência rápida de tipos e funções da std
- Glossário: Zero-Cost Abstraction — entenda por que iteradores Rust são tão rápidos
- Benchmarks em Go — compare com a abordagem de benchmark nativa de Go