El problema fundamental que aborda este artículo es cómo las decisiones de diseño de software, particularmente en capas críticas de manejo de solicitudes, deben co-evolucionar con las arquitecturas de hardware subyacentes para maximizar la eficiencia y el rendimiento. Históricamente, el aumento de la densidad de núcleos en las CPUs ha sido acompañado por mejoras en la jerarquía de caché para mantener la localidad de datos. Sin embargo, las arquitecturas modernas, como los procesadores AMD EPYC Turin, están realizando un trade-off explícito: priorizar un conteo masivo de núcleos y throughput total sobre la caché L3 por núcleo.

Este cambio arquitectónico expone una vulnerabilidad en stacks de software legados que dependen implícitamente de grandes cachés L3 para mantener baja latencia. La tesis central es que para desbloquear el potencial de rendimiento de estas nuevas CPUs, es imperativo rediseñar el software para que sea menos sensible a la latencia de acceso a memoria, optimizando los patrones de acceso y reduciendo la huella de memoria activa. La reescritura de FL1 a FL2 en Rust es un ejemplo de cómo una reingeniería profunda del software puede alinear la aplicación con las características del hardware, transformando una limitación en una ventaja competitiva.

Arquitectura del Sistema

La arquitectura original, FL1, se basaba en NGINX y LuaJIT, una combinación que, si bien era eficiente para su época, exhibía patrones de acceso a memoria que dependían fuertemente de la localidad de caché L3. Esto se evidenció por el aumento drástico de las tasas de fallos de caché L3 y la latencia de acceso a DRAM en las nuevas CPUs Turin, que ofrecen significativamente menos caché L3 por núcleo.

La nueva arquitectura, FL2, es una reescritura completa en Rust, construida sobre los frameworks Pingora y Oxy. Esta transición implica un cambio fundamental en cómo se manejan las solicitudes y se accede a la memoria. Rust, con su seguridad de memoria y control de bajo nivel, permite a los ingenieros diseñar patrones de acceso a memoria más predecibles y eficientes. Esto incluye la reducción de asignaciones dinámicas, la optimización de la disposición de datos en memoria y la minimización de la huella de trabajo (working set) para que quepa en cachés más pequeñas. El resultado es un sistema con menos fallos de caché y, por lo tanto, menor latencia, incluso en arquitecturas de CPU con menor caché L3 por núcleo. La modularidad y el sistema de tipos estricto de Rust también contribuyen a una mayor velocidad de desarrollo y seguridad, que fueron motivaciones iniciales para el proyecto FL2, además del rendimiento.

CapaTecnologíaJustificación
compute AMD EPYC 5th Gen Turin (Zen 5) Procesadores de nueva generación para la capa de manejo de solicitudes, ofreciendo mayor conteo de núcleos (hasta 192) y mejoras de IPC, pero con menor caché L3 por núcleo.
compute AMD EPYC 4th Gen Genoa-X (Zen 4) Procesadores de generación anterior, con 96 núcleos y una gran caché L3 (12MB por núcleo) gracias a 3D V-Cache, que era ideal para el stack FL1.
data-processing FL1 (NGINX + LuaJIT) Capa de manejo de solicitudes legada, que exhibía alta dependencia de la caché L3 y patrones de acceso a memoria ineficientes para arquitecturas con menor caché por núcleo.
data-processing FL2 (Rust + Pingora + Oxy) Nueva capa de manejo de solicitudes reescrita en Rust, diseñada con patrones de acceso a memoria optimizados para arquitecturas con menor caché L3 por núcleo, permitiendo un escalado lineal del throughput.
observability AMD uProf Herramienta utilizada para recolectar contadores de rendimiento de CPU y datos de profiling para diagnosticar problemas de caché y latencia en FL1.
orchestration AMD Platform Quality of Service (PQOS) Extensiones de hardware utilizadas para la regulación fina de recursos compartidos como caché y ancho de banda de memoria, permitiendo la asignación dedicada de caché L3 a FL1 en experimentos de optimización.

Trade-offs

Ganancias
  • ▲▲ Throughput total
  • Performance/watt
  • Costo total de propiedad (TCO)
Costes
  • ▲▲ Caché L3 por núcleo
  • Latencia de acceso a memoria para software no optimizado

Fundamentos Teóricos

Este escenario resalta la importancia de la jerarquía de memoria, un concepto fundamental en la arquitectura de computadoras. El principio de localidad, tanto temporal como espacial, es clave para el rendimiento de los sistemas. Cuando un programa exhibe buena localidad, los datos y las instrucciones que necesita están a menudo en la caché, lo que resulta en accesos rápidos. La reducción de la caché L3 por núcleo en las CPUs Turin, sin un cambio correspondiente en el software, viola la expectativa de localidad de FL1, forzando accesos más lentos a la DRAM.

El problema de la latencia de acceso a memoria ha sido estudiado extensamente, por ejemplo, en trabajos sobre la 'Memory Wall' que describen la creciente brecha de rendimiento entre las CPUs y la memoria principal. La solución de Cloudflare con FL2, al optimizar los patrones de acceso a memoria y reducir la dependencia de grandes cachés, es una aplicación práctica de principios de diseño de software consciente de la arquitectura de memoria, buscando minimizar los fallos de caché y el costo de los accesos a memoria fuera de la caché. Esto se alinea con técnicas de optimización de rendimiento que se encuentran en la literatura de sistemas operativos y compiladores, donde la gestión eficiente de la memoria es crucial.