Introducción: por qué Rust ya es rápido, pero no es el límite
\n\nRust es, con razón, considerado uno de los lenguajes de programación de sistemas más eficientes. Gracias a las abstracciones de costo cero (zero-cost abstractions) y un estricto sistema de propiedad, el código en Rust a menudo funciona tan rápido como el código equivalente en C o C++. Sin embargo, incluso en Rust hay "trampas" que pueden ralentizar una aplicación, especialmente para desarrolladores principiantes.
\n\nEn este artículo, analizaremos los principios clave y las técnicas prácticas para optimizar aplicaciones Rust. Aprenderá a perfilar código, evitar asignaciones innecesarias, trabajar eficientemente con colecciones y utilizar el poder del compilador. El material está dirigido a principiantes, por lo que todos los ejemplos serán lo más simples y claros posible.
\n\n¿Listo para hacer su código Rust aún más rápido? ¡Vamos allá!
\n\n1. Perfilado: primero medimos, luego optimizamos
\n\nAntes de optimizar algo, debemos entender qué es lo que realmente ralentiza. En Rust, existen excelentes herramientas para ello.
\n\n1.1. Benchmarks con cargo bench
\n\nLa forma estándar es usar benchmarks. Permiten medir el tiempo de ejecución de funciones específicas.
\n\n// En el archivo benches/my_bench.rs\nuse criterion::{criterion_group, criterion_main, Criterion};\n\nfn fibonacci(n: u64) -> u64 {\n match n {\n 0 => 1,\n 1 => 1,\n 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(20)));\n}\n\ncriterion_group!(benches, bench_fib);\ncriterion_main!(benches);\n\n\nPara ejecutarlo, agregue a Cargo.toml:
[dev-dependencies]\ncriterion = "0.5"\n\n[[bench]]\nname = "my_bench"\nharness = false\n\nLuego ejecute cargo bench. Obtendrá cifras precisas: tiempo de ejecución, desviación estándar y comparación con ejecuciones anteriores.
1.2. Perfilado de CPU y memoria
\n\nPara un análisis más profundo, utilice:
\n- \n
- perf (Linux) — perfilador a nivel de kernel. \n
- Valgrind — para buscar fugas de memoria y analizar la caché. \n
- flamegraph — visualización de los "puntos calientes" del código. \n
Ejemplo de generación de flamegraph:
\ncargo install flamegraph\ncargo flamegraph --bin my_app\n# Abrimos flamegraph.svg en el navegador\n\n\nFlamegraph mostrará qué funciones consumen más tiempo. Esto ayudará a centrarse en lo principal.
\n\n2. Gestión de memoria: evitando asignaciones innecesarias
\n\nLas asignaciones en el heap son una de las causas más comunes de código lento. Rust otorga control total sobre la memoria, pero los principiantes a menudo abusan de Box, Vec y String.
2.1. Use la pila en lugar del heap
\n\nSiempre que sea posible, almacene datos en la pila. Por ejemplo, en lugar de Vec para un conjunto fijo pequeño, use un array:
// Lento: asignación en el heap\nlet data: Vec<i32> = vec![1, 2, 3, 4, 5];\n\n// Rápido: pila\nlet data: [i32; 5] = [1, 2, 3, 4, 5];\n\n\n2.2. Preasignación de memoria
\n\nSi el tamaño de la colección se conoce de antemano, use with_capacity:
// Mal: muchas reasignaciones\nlet mut v = Vec::new();\nfor i in 0..1000 {\n v.push(i);\n}\n\n// Bien: una sola asignación\nlet mut v = Vec::with_capacity(1000);\nfor i in 0..1000 {\n v.push(i);\n}\n\n\n2.3. Evite la clonación
\n\nClonar cadenas y estructuras grandes es una operación costosa. Use préstamo (&str en lugar de String):
fn process(s: &str) { /* ... */ }\n\n// Mal: clonación\nlet s = String::from("hello");\nprocess(&s.clone());\n\n// Bien: préstamo\nprocess(&s);\n\n\n