Blazing Rust
Mejora el rendimiento medido mientras preservas el comportamiento. Mantén el cambio más simple que produzca una ganancia repetible en entradas representativas.
Mide antes de editar
- Identifica la carga de trabajo, la métrica objetivo y las restricciones de corrección. Lee la ruta crítica y sus llamantes. Respeta el toolchain del proyecto, la versión mínima soportada de Rust, las características y los objetivos de despliegue. Una solicitud de explicación autoriza la medición y el perfilado, no las ediciones.
- Captura una línea base con el perfil de producción optimizado del proyecto, normalmente
release. Registra la entrada, el comando exacto, el toolchain, las banderas, la CPU y el número de hilos. Usa tamaños y distribuciones de entrada realistas, incluyendo casos pequeños y adversos. - Encuentra lo que limita el rendimiento. Perfila tiempo de CPU, asignaciones, E/S o esperas de bloqueo según corresponda. Para bloqueos asíncronos, inspecciona la planificación de tareas y el crecimiento de colas con trazado consciente del runtime. Usa las herramientas existentes primero; la sección de perfilado enumera las comunes y cómo mantener símbolos en builds de release. Si el perfilado no está disponible, usa mediciones dirigidas y declara los límites de la evidencia.
- Enuncia una hipótesis y realiza un cambio medible. Ejecuta pruebas de corrección enfocadas y repite el mismo benchmark. Verifica el resultado en la carga de trabajo completa antes de afirmar una mejora visible para el usuario.
- Mantén las ganancias respaldadas. Deshaz solo tus ediciones fallidas, preservando el trabajo no relacionado. Detente cuando se alcance el objetivo o los cambios posteriores carezcan de evidencia.
Haz que el benchmark sea creíble
- Separa la configuración, compilación, generación de entrada e inicio del proceso de la operación a menos que pertenezcan a la métrica objetivo. Consume los resultados. Para microbenchmarks, usa las barreras de optimización de la biblioteca de benchmarks o
std::hint::black_boxen entradas y salidas. Estas barreras son de mejor esfuerzo, así que inspecciona resultados inverosímiles. - Compara trabajo y salidas equivalentes. Mantén fijos el toolchain, perfil, características, asignador, configuración de CPU y número de hilos a menos que uno sea la variable bajo prueba. Calienta al medir estado estable. Mide arranques en frío por separado cuando sea relevante.
- Repite las ejecuciones e informa el número de muestras, la mediana y la variabilidad. Alterna ejecuciones de línea base y candidato cuando la deriva sea importante. Una diferencia dentro del ruido no establece una victoria.
- Iguala las métricas a la tarea. Para servicios, mide latencia p95/p99 con una carga declarada, rendimiento, errores y uso de recursos. Verifica que la latencia reducida no provino de trabajo descartado o una cola creciente.
- Reutiliza benchmarks existentes. Agrega un caso pequeño de regresión cuando la nueva lógica lo necesite. No instales herramientas, agregues dependencias ni cambies configuraciones de compilación sin la autorización de la tarea. Nunca inventes mediciones.
Elige cambios basados en evidencia
Comienza con trabajo innecesario y algoritmos. Considera cambios de bajo nivel solo cuando el cuello de botella restante los requiera. Lee la sección correspondiente de la guía de técnicas cuando un cuello de botella medido necesite más detalle.
| Costo observado | Cambio candidato y verificaciones |
|---|---|
| Escaneos o cómputo repetidos | Mejora el algoritmo o la elección de la colección. Elimina análisis, ordenamientos, serializaciones y búsquedas duplicados. Considera la invalidación de caché y la memoria si el almacenamiento en caché ayuda. |
| Asignación o copia | Toma prestado &str y slices, mueve valores owned, reutiliza buffers, y reserva capacidades creíbles. Prueba clone_from al reemplazar un valor asignado. Evita colecciones intermedias. clone puede ser barato para handles compartidos, así que mide su costo. |
| Búsquedas en colecciones | Usa claves prestadas y APIs entry donde eviten trabajo repetido. Compara vectores densos o datos ordenados cuando el patrón de acceso se ajuste. Preserva la resistencia a ataques de colisión para claves no confiables al cambiar hashers. |
| Ordenamiento o eliminación | Usa select_nth_unstable para selección, sort_unstable cuando el orden de elementos iguales sea irrelevante, y sort_by_cached_key para claves costosas. Usa swap_remove solo cuando el orden de los elementos sea irrelevante. |
| Fallos de caché o valores grandes | Compara arrays de estructuras con estructuras de arrays. Verifica size_of, align_of y fallos de caché. El almacenamiento en línea puede aumentar la copia. Considera empaquetar cargas útiles grandes de enum raras, contabilizando asignaciones e indirección. |
| Llamadas o bucles pequeños y calientes | Prefiere iteración ordinaria. Ayuda al compilador a eliminar comprobaciones de límites con slices, iteración o aserciones de rango. Inspecciona el ensamblador optimizado antes de inlinear o desenrollar. Prueba entradas vacías, colas, desbordamiento y tamaño del código. |
| Búsqueda de texto | Reutiliza expresiones regulares compiladas y solicita solo la información de coincidencia necesaria. Compara búsquedas especializadas de bytes cuando la semántica de codificación lo permita. Evita reconstruir el estado de búsqueda dentro de bucles calientes. |
| Cómputo vectorizable | Verifica la auto-vectorización antes del SIMD explícito. Inspecciona el código generado. Protege instrucciones específicas del objetivo donde sea necesario y mantén un fallback escalar. Prueba colas y CPUs representativas. Preserva la alineación, límites y comportamiento numérico. |
| Trabajo CPU independiente | Compara ejecución serial con paralelismo acotado. Mide costo de planificación, contención, ancho de banda, número de hilos y consumo total de CPU. Ajusta el tamaño de la tarea solo cuando la sobrecarga importe. Preserva la semántica de reducción. |
| E/S o bloqueos asíncronos | Reutiliza buffers de línea, agrupa E/S pequeña y maneja errores de vaciado. Preserva latencia, manejo de nuevas líneas y durabilidad. Mantén el trabajo bloqueante o pesado de CPU fuera de los workers asíncronos. Acota la concurrencia y las colas. Preserva orden, contrapresión y cancelación. |
| Límites de compilación o generación de código | Compara configuraciones existentes con thin LTO. Prueba fat LTO y codegen-units = 1 por separado antes de combinarlos. Incluye el tiempo de compilación y el tamaño del binario. Usa banderas específicas de CPU solo cuando el hardware de despliegue lo permita. |
Preserva la corrección
Mantén intactas las API públicas, el manejo de errores, la validación, el orden, la serialización y los contratos FFI. Preserva el comportamiento de desbordamiento de enteros y punto flotante. La representación de enum o el orden de las variantes puede cambiar el diseño y los discriminantes; ninguno garantiza un enum más pequeño o rápido.
Prefiere Rust seguro y dependencias existentes. Introducir código inseguro requiere una necesidad medida, invariantes de seguridad explícitos y pruebas enfocadas. Usa Miri o sanitizers donde sean compatibles y relevantes. Nunca sacrifiques validación o solidez por velocidad de benchmark, y no trates las pruebas exitosas como prueba de solidez.
Reporta el resultado
Indica el cuello de botella, el cambio y la decisión de mantener o revertir. Incluye comandos reproducibles, resultados antes/después con variabilidad, cifras relevantes de asignación o memoria, verificaciones de corrección y compensaciones. Distingue las ganancias de tiempo real multinúcleo de la eficiencia de un solo núcleo. Nombra las afirmaciones no medidas y las plataformas no verificadas.