¿Por qué Rust es rápido, pero no siempre?
\nRust es un lenguaje que promete el rendimiento de C++ con la seguridad de la memoria. Pero incluso en Rust se puede escribir código lento. Un par de decisiones incorrectas y su servicio comienza a «ir lento» bajo carga. La buena noticia: Rust le brinda todas las herramientas para encontrar y eliminar los cuellos de botella. En este artículo, analizaremos cómo perfilar código en Rust y qué técnicas de optimización realmente funcionan.
\n\nNo hablaremos de forma abstracta. Verá ejemplos reales de código, aprenderá a usar perf, flamegraph, valgrind y los benchmarks integrados. Después de leerlo, podrá realizar una auditoría de rendimiento de su proyecto por su cuenta.
Herramientas para perfilar Rust
\nAntes de optimizar algo, hay que entender qué es lo que realmente va lento. Aquí hay tres herramientas prácticas que le ayudarán a mirar dentro del programa.
\n\n1. Benchmarks con cargo bench
\nLa forma estándar de medir el rendimiento es escribir un benchmark. En Rust se utiliza la crate criterion o el framework de pruebas integrado. Ejemplo:
use criterion::{black_box, criterion_group, criterion_main, Criterion};\n\nfn fibonacci(n: u64) -> u64 {\n match n {\n 0 => 0,\n 1 => 1,\n _ => fibonacci(n - 1) + fibonacci(n - 2),\n }\n}\n\nfn bench_fib(c: &mut Criterion) {\n c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));\n}\n\ncriterion_group!(benches, bench_fib);\ncriterion_main!(benches);\n\n\nEjecución: cargo bench. Como resultado obtendrá el tiempo de ejecución, la desviación y la comparación con ejecuciones anteriores. Esta es la base con la que debería comenzar cualquier optimización.
2. Perfilado con perf y flamegraph
\nCuando necesite saber qué funciones consumen la CPU, use perf. Recolecte los datos y luego visualícelos en forma de flamegraph:
# Recolección de datos\nperf record -g ./target/release/my_app\n# Generación del flamegraph\nperf script | stackcollapse-perf.pl | flamegraph.pl > profile.svg\n\n\nEl flamegraph le mostrará las secciones «calientes» del código. Cuanto más ancho sea el bloque, más tiempo se gasta en esa función. Esta es la forma más visual de encontrar un cuello de botella.
\n\n3. Búsqueda de fugas con Valgrind
\nRust protege contra fugas de memoria a nivel de compilación, pero no contra todas. Por ejemplo, si usa Rc o Arc con referencias cíclicas, la memoria puede no liberarse. Valgrind le ayudará a detectarlo:
valgrind --tool=massif ./target/debug/my_app\n\n\nMassif mostrará cuánta memoria consume el programa en cada momento. Si el gráfico no cae después de finalizar la ejecución, busque una fuga.
\n\nTécnicas prácticas de optimización de código en Rust
\nAhora que sabe qué buscar, analicemos cómo solucionarlo. Aquí hay cinco métodos probados que realmente aceleran el código.
\n\n1. Abstracciones de costo cero: use los iteradores correctamente
\nLos iteradores en Rust se compilan en código tan eficiente como los bucles manuales. Pero solo si no hace copias innecesarias. Compare:
\n\n// Mal: creamos un Vec, copiamos, filtramos\nlet result: Vec<u32> = data.iter().filter(|x| x % 2 == 0).collect();\n// Bien: trabajamos con el iterador sin colecciones intermedias\nlet sum: u32 = data.iter().filter(|x| x % 2 == 0).sum();\n\n\nLa segunda opción no asigna memoria para un vector temporal. Si solo necesita un valor, no lo coleccione.
\n\n2. Almacenamiento en caché de resultados
\nSi una función se llama repetidamente con los mismos argumentos, almacene en caché el resultado. Use HashMap o la crate lru para limitar el tamaño de la caché:
use std::collections::HashMap;\n\nfn expensive_calc(x: u64) -> u64 {\n // ... cálculos largos\n x * x\n}\n\nfn c