En sistemas multinúcleo, la forma en que se organizan los campos de una estructura en memoria determina si el código se ejecuta en paralelo de forma eficiente o se serializa silenciosamente por el protocolo de coherencia. Este artículo, primera parte de una serie sobre diseño de sistemas de bajo nivel en Rust, aborda precisamente esa cuestión usando como ejemplo un anillo de productor-consumidor único (SPSC).
El punto de partida es la línea de caché, normalmente 64 bytes en x86-64 y en la mayoría de núcleos AArch64, que es la unidad con la que se factura el tráfico de coherencia entre núcleos. El primer movimiento de diseño consiste en agrupar los campos en zonas según quién los escribe y con qué frecuencia: zona caliente del productor, zona caliente del consumidor y zona fría. El false sharing aparece cuando dos núcleos escriben en campos distintos que casualmente comparten línea, haciendo que las cachés se invaliden mutuamente aunque no exista contención lógica.
En la implementación del anillo, los cursores tail y head, junto con sus respectivas copias caché, se alinean con CacheAligned para evitar ese solapamiento, mientras que campos como closed, metrics o config se empaquetan deliberadamente sin relleno, ya que nadie compite por ellos en el camino caliente. El atributo #[repr(C)] resulta imprescindible: garantiza que el compilador respete el orden de declaración de los campos y no los reordene para minimizar el padding, lo que podría colapsar las zonas separadas. Además, se recuerda que repr(C) no congela recursivamente el layout de structs anidados, por lo que cada tipo con división caliente/fría debe llevar su propio repr(C).
La regla de los 128 bytes que el título adelanta se apoya en este mismo principio: separar zonas en lugar de rellenar campos fríos.
