Como Medir Tempo de Execução em Rust

Aprenda a medir tempo de execução em Rust com Instant::now(), elapsed() e benchmarks com criterion. Guia completo para otimizar performance do seu código.

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 que sort() por usar menos memória.
  • Pre-aloque com with_capacity() quando souber o tamanho aproximado.
  • push_str() é mais eficiente que format!() para construir strings.
  • Use criterion para benchmarks sérios — ele calcula estatísticas, detecta outliers e gera relatórios HTML.

Veja Também