¿Por qué Rust requiere un enfoque especial para el benchmarking?
Rust es famoso por su velocidad, pero incluso el lenguaje más rápido puede ralentizarse con una implementación deficiente. ¿Alguna vez has escrito un algoritmo en Rust pensando: «¡Esto será ultrarrápido!», y luego al ejecutarlo no entendías dónde se perdieron los microsegundos? ¿Te suena familiar?
El benchmarking en Rust no es solo iniciar un cronómetro y medir el tiempo. Es toda una ciencia: hay que tener en cuenta las optimizaciones del compilador, el almacenamiento en caché del procesador e incluso cómo Rust gestiona la memoria. En este artículo analizaremos cómo hacer benchmarking correctamente de programas en Rust para que obtengas cifras honestas, no resultados aleatorios.
Recorreremos el camino desde mediciones simples con std::time hasta la herramienta profesional criterion, que se utiliza en proyectos reales. ¡Allá vamos!
1. La forma más sencilla: std::time::Instant
Si necesitas «tantear» rápidamente el rendimiento de un fragmento de código, sin instalar crates, simplemente inserta la medición directamente en main. Pero hay un truco: el optimizador de Rust puede «eliminar» tu código si no utiliza el resultado.
use std::time::Instant;
fn main() { let start = Instant::now();
// Tu código a medir let mut sum = 0u64; for i in 0..1_000_000 { sum += i; }
let duration = start.elapsed(); println!("Tiempo de ejecución: {:?}", duration); // ¡Asegúrate de usar el resultado! println!("Suma: {}", sum);}¿Por qué no se puede medir con una sola ejecución?
Una sola ejecución es una lotería. El sistema puede estar ocupado, el procesador puede estar haciendo throttling, la caché está fría. Un benchmark correcto en Rust debe:
- Calentar el código (warm-up) — varias iteraciones antes de la medición
- Ejecutar el código muchas veces (miles de iteraciones)
- Promediar los resultados
La implementación manual de esto es un camino hacia los errores. Por eso la comunidad de Rust creó criterion.
2. Benchmarking profesional: el crate Criterion
Criterion.rs es el estándar de facto para benchmarking en Rust. Se utiliza en el propio compilador rustc y en cientos de crates. ¿Por qué es tan bueno?
- Calentamiento automático y procesamiento estadístico
- Gráficos y comparación con ejecuciones anteriores
- Detección de regresiones (si el código se vuelve más lento, lo sabrás)
- Soporte para benchmarks parametrizados
Instalación y configuración
Añade a Cargo.toml:
[dev-dependencies]criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]name = "my_benchmark"harness = falseCrea el archivo benches/my_benchmark.rs:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
// Función a evaluarfn 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 — evita que el optimizador elimine el código b.iter(|| fibonacci(black_box(20))) });}
criterion_group!(benches, bench_fibonacci);criterion_main!(benches);Ejecución: cargo bench. Verás algo como:
fibonacci 20 time: [24.567 µs 24.789 µs 25.012 µs]Los tres números son el intervalo de confianza (límite inferior, medio, superior). Si el intervalo es estrecho, el benchmark es estable.
3. El ingrediente secreto: black_box
Puede que hayas notado black_box en el ejemplo. Esta es una función mágica de criterion que evita que LLVM optimice los argumentos o el resultado. Sin ella, tu benchmark podría mostrar 0 ns, porque el compilador simplemente calculó la constante en tiempo de compilación.
Ejemplo p