Lección 10 de 19 · Lectura y práctica
Profiling y Benchmarking con Criterion
Concepto claveEn sistemas de baja latencia y alta seguridad, cada microsegundo cuenta y cada operación debe ser predecible. El profiling es el proceso de medir el rendimiento de tu código para identificar cuellos de botella,.
Del concepto a la práctica
Taller de esta lección
Un benchmark válido cambia una sola variable y mantiene iguales entrada, compilación y operación; el promedio no describe por sí solo colas de latencia ni variabilidad.
Ejemplo resuelto
Compara sumar un slice de cuatro enteros mediante un bucle y mediante un iterador. La salida funcional esperada es 10 en ambos casos; el benchmark decide el coste, no la intuición.
fn suma(xs: &[u32]) -> u32 { xs.iter().copied().sum() }Pruébalo
Define una entrada fija
[1, 2, 3, 4]fuera de la medición.Mide cada alternativa con varias iteraciones.
Registra unidad, compilación y tamaño de entrada antes de comparar.
Comprueba tu resultado
Ambas implementaciones deben devolver 10.
Error frecuente: construir la entrada dentro del tramo medido; corrección: prepara datos antes y evita que el compilador elimine el resultado.
Ejercicio: repite con ocho valores y anota mediana y peor observación. Resultado esperado: la suma funcional es 36; cualquier diferencia temporal debe quedar identificada como medición, no como cifra universal.
Harness de Criterion
Crea un benchmark de Cargo con harness=false y el archivo benches/suma.rs:
use criterion::{criterion_group, criterion_main, Criterion};
use std::hint::black_box;
fn suma(xs: &[u32]) -> u32 { xs.iter().copied().sum() }
fn medir(c: &mut Criterion) {
let xs = [1u32, 2, 3, 4];
assert_eq!(suma(&xs), 10);
c.bench_function("suma4", |b| b.iter(|| black_box(suma(black_box(&xs)))));
}
criterion_group!(benches, medir);
criterion_main!(benches);Elige una versión de Criterion compatible, con lockfile. Criterion resume mediciones de benchmark, no proporciona automáticamente el p99 de solicitudes del microservicio. Para ese percentil conserva muestras de peticiones y define el método; el peor valor observado de una muestra tampoco es p99. El resultado 36 supone datos [1,2,3,4,5,6,7,8].

Mapa de prácticaUna condición decide qué camino ejecuta el programa. Un bucle repite una operación mientras queden elementos, y termina cuando la lista se agota. Separar ambas ideas ayuda a seguir el flujo y comprobar casos límite.
Ver la infografía en tamaño completo ↗Fuentes para profundizar
Concepto clave
En sistemas de baja latencia y alta seguridad, cada microsegundo cuenta y cada operación debe ser predecible. El profiling es el proceso de medir el rendimiento de tu código para identificar cuellos de botella, mientras que el benchmarking compara diferentes implementaciones bajo condiciones controladas. Imagina que eres un ingeniero de Fórmula 1: el profiling es como analizar los datos de telemetría para ver dónde el coche pierde tiempo en la pista, y el benchmarking es probar diferentes configuraciones de alerones en el túnel de viento para elegir la más rápida.
Criterion es la herramienta estándar en Rust para benchmarking estadístico. A diferencia de mediciones simples con std::time::Instant, Criterion ejecuta múltiples iteraciones, aplica análisis estadístico para eliminar ruido, y genera informes visuales. Esto es crucial en sistemas críticos donde variaciones de nanosegundos pueden afectar SLA (Service Level Agreements) o crear vulnerabilidades de timing.
Cómo funciona en la práctica
Primero, añade Criterion a tu Cargo.toml en la sección dev-dependencies:
[dev-dependencies]
criterion = "0.5"Crea un archivo benches/my_benchmark.rs en tu proyecto. Aquí un ejemplo que compara dos algoritmos de hash para seguridad:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use sha2::{Sha256, Digest};
use blake3::Hasher;
fn bench_sha256(c: &mut Criterion) {
let data = vec![0u8; 1024]; // 1KB de datos
c.bench_function("sha256_1kb", |b| {
b.iter(|| {
let mut hasher = Sha256::new();
hasher.update(black_box(&data));
hasher.finalize()
})
});
}
fn bench_blake3(c: &mut Criterion) {
let data = vec![0u8; 1024];
c.bench_function("blake3_1kb", |b| {
b.iter(|| {
let mut hasher = Hasher::new();
hasher.update(black_box(&data));
hasher.finalize()
})
});
}
criterion_group!(benches, bench_sha256, bench_blake3);
criterion_main!(benches);Ejecuta con cargo bench. Criterion ejecutará cada función miles de veces, calculará estadísticas como media, desviación estándar y outliers, y generará un informe en target/criterion/report/index.html. Usa black_box para evitar que el compilador optimice código que no debería, simulando una entrada real.
Caso de estudio
En un sistema de trading de alta frecuencia, necesitas validar firmas digitales en menos de 10 microsegundos. El equipo probó dos bibliotecas: ring (de BoringSSL) y rustls. Configuraron Criterion para medir la verificación de firmas ECDSA P-256 con datos de 256 bytes.
| Biblioteca | Tiempo medio (ns) | Desviación estándar (ns) | Outliers |
|---|---|---|---|
| ring | 8,450 | 120 | 0.1% |
| rustls | 9,200 | 350 | 0.5% |
La baja desviación estándar de ring indica mayor predictibilidad, crucial para baja latencia. Aunque la diferencia media es solo 750ns, en 10,000 operaciones/segundo, ring ahorra 7.5ms por segundo, reduciendo colas y mejorando throughput.
El informe de Criterion mostró que ring usaba ensamblador optimizado, mientras rustls tenía más branches. Decidieron usar ring para firmas críticas y rustls para conexiones TLS menos sensibles.
Errores comunes
- No aislar el código benchmarked: Medir operaciones que incluyen E/S o allocations del sistema. Usa
black_boxy prepara datos en el setup. - Ignorar el ruido del sistema: Ejecutar benchmarks en máquinas con carga variable. Usa máquinas dedicadas o tools como
tasksetpara fijar CPUs. - Comparar sin contexto estadístico: Decidir basado en una sola ejecución. Criterion proporciona intervalos de confianza; si se solapan, las diferencias pueden no ser significativas.
- Olvidar el overhead de medición: En operaciones de nanosegundos, el loop de benchmarking añade overhead. Usa
Bencher::iter_custompara mediciones muy finas. - No validar seguridad: Optimizar a costa de constant-time operations. En criptografía, verifica que los benchmarks no introduzcan side-channels.
Checklist de dominio
- Configurar Criterion en un proyecto Rust con benchmarks aislados.
- Interpretar reportes HTML: media, desviación, outliers, y comparaciones.
- Usar
black_boxcorrectamente para evitar optimizaciones no deseadas. - Diseñar benchmarks que reflejen cargas reales de sistemas de baja latencia.
- Integrar benchmarking en CI/CD para detectar regresiones de performance.
- Distinguir entre variabilidad aceptable y problemas de performance.
- Combinar profiling (con tools como perf o flamegraph) y benchmarking para optimización completa.
Benchmark de un Cache LRU para Baja Latencia
Implementa y compara dos implementaciones de cache LRU (Least Recently Used) para un sistema de API que requiere respuestas en menos de 1ms.
- Crea un nuevo proyecto Rust con
cargo new lru_benchmark. - Añade estas dependencias a
Cargo.toml:[dependencies] lru = "0.11" [dev-dependencies] criterion = "0.5" - En
src/lib.rs, implementa una versión simple de LRU usandostd::collections::VecDequeystd::collections::HashMapcon capacidad fija de 1000 elementos. - En
benches/cache_bench.rs, crea un benchmark con Criterion que compare:- Tu implementación custom de LRU.
- La crate
lrucon la misma capacidad.
- Ejecuta
cargo benchy analiza el reporte entarget/criterion. - Documenta tus hallazgos: ¿cuál es más rápido? ¿Cuál tiene menor desviación estándar? ¿Cuál elegirías para baja latencia?
Pistas
- Usa
black_boxen los datos de acceso aleatorio para simular entrada real. - Configura el LRU con capacidad fija para evitar allocations durante el benchmark.
- Considera usar
criterion_groupconBenchmarkIdpara comparaciones claras.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.
Sigue con Rust para Sistemas de Baja Latencia y Alta Seguridad
Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.
Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.