Una caché puede utilizarse para mejorar el rendimiento del acceso a un determinado recurso. Cuando existen varias cachés que almacenan copias del mismo recurso —por ejemplo, las cachés de datos locales de varias CPU en un sistema multiproceso— pueden aparecer inconsistencias si una copia se modifica y las demás no se actualizan. La coherencia de caché se refiere al conjunto de mecanismos y políticas diseñados para garantizar que todas las cachés que contienen copias de un mismo dato mantienen una vista consistente y la integridad de los datos. La coherencia de caché es un caso particular de la coherencia de la memoria y es esencial para que programas paralelos funcionen correctamente.
Puede haber problemas si hay muchas cachés de un recurso de memoria común, ya que los datos en una caché pueden quedar desactualizados o inconsistentes respecto a otras copias. Un ejemplo típico es la caché de las CPUs en un sistema multiproceso. Como se ilustra frecuentemente en diagramas, si un núcleo (cliente superior) tiene una copia de un bloque de memoria proveniente de una lectura anterior y otro núcleo (cliente inferior) cambia ese bloque, el primero puede quedarse con una copia obsoleta sin saberlo. La coherencia de caché gestiona estos conflictos y mantiene la consistencia entre las cachés y la memoria principal.
Cómo se producen los problemas de coherencia
Un escenario típico:
- CPU A lee la dirección X y conserva en su caché el valor V0.
- CPU B escribe la dirección X y cambia el valor a V1.
- Si CPU A vuelve a leer X desde su caché sin que haya algún mecanismo que invalide o actualice su copia, obtendrá V0 (obsoleto) en lugar de V1.
Sin coherencia, esto genera errores sutiles en programas paralelos: resultados incorrectos, condiciones de carrera o violaciones de invariantes. Además de escrituras reales, existen casos de falsa compartición (false sharing): dos variables independientes ubicadas en la misma línea de caché producen tráfico de coherencia innecesario si diferentes hilos las modifican.
Políticas básicas y técnicas
Las soluciones a la coherencia combinan políticas de escritura, protocolos de coordinación y mecanismos de sincronización. Estas son las categorías principales:
- Write-through vs write-back: en write-through cada escritura se propaga inmediatamente a la memoria principal (y opcionalmente a otras cachés), facilitando coherencia pero generando más tráfico. En write-back las escrituras quedan en la caché y se escriben a memoria más tarde, reduciendo tráfico pero obligando a protocolos para mantener la coherencia.
- Invalidate vs update: al modificar un bloque, el protocolo puede invalidar las copias en otras cachés (invalidate) o enviar la nueva versión a las demás (update). Invalidar suele reducir tráfico cuando hay muchas lecturas; actualizar evita lecturas inválidas pero puede costar más ancho de banda.
- Snooping (observación del bus): cada caché vigila las transacciones en un bus compartido y responde a peticiones de otras caches. Es eficiente en sistemas con pocos núcleos y un bus común.
- Directory-based (directorio): se mantiene una estructura que registra qué caches tienen copia de cada línea de memoria; las actualizaciones o invalidaciones se envían solo a las caches relevantes. Escala mejor en máquinas con muchos nodos o en arquitecturas NUMA.
Protocolos de coherencia
Existen diversos protocolos prácticos que implementan las políticas anteriores. Algunos comunes:
- MSI (Modified, Shared, Invalid): estado básico que indica si una línea está modificada, compartida o inválida.
- MESI (Modified, Exclusive, Shared, Invalid): añade el estado Exclusive para optimizar lecturas exclusivas sin tráfico adicional.
- MOESI y variantes: introducen el estado Owner para reducir escrituras a memoria y compartir datos modificados directamente entre caches.
Estos protocolos definen transiciones de estado y mensajes entre caches (lectura compartida, lectura exclusiva, escritura, invalidación, respuesta de datos, etc.).
Consistencia de memoria vs coherencia de caché
Es importante distinguir coherencia y consistencia: la coherencia asegura que todas las copias de una misma ubicación de memoria evolucionan de forma consistente (no hay versiones contradictorias). La consistencia de memoria es una especificación de ordenamiento de las operaciones visibles entre hilos (por ejemplo, sequential consistency, relaxed consistency). Un sistema puede tener coherencia de caché pero ofrecer modelos de consistencia más relajados, lo que exige al programador entender las garantías de orden y usar barreras/operaciones atómicas cuando corresponda.
Problemas prácticos y costes
- Tráfico de coherencia: invalidaciones y actualizaciones consumen ancho de banda y latencia, afectando rendimiento.
- Escalabilidad: técnicas de snooping funcionan bien con pocos núcleos; en sistemas con muchos núcleos o nodos distribuidos se prefieren directorios o soluciones jerárquicas.
- Falsa compartición: el diseño de estructuras de datos sin tener en cuenta el tamaño de línea de caché puede provocar caídas de rendimiento.
- Complejidad de hardware: protocolos avanzados y coherencia en niveles múltiples de caché elevan la complejidad del diseño del procesador.
Soluciones y buenas prácticas
Los mecanismos implementados en hardware se complementan con técnicas de software:
- Sincronización adecuada: usar primitivas atómicas, locks, semáforos y barreras para coordinar acceso a datos compartidos; estas operaciones suelen implicar acciones explícitas de coherencia (inval/flush).
- Estructuras de datos amigables con caché: reducir el sharing inter-hilos, agrupar datos por afinidad y evitar variables compartidas calientes.
- Alineación y padding: separar variables frecuentemente modificadas en distintas líneas de caché para evitar falsa compartición.
- Minimizar escrituras: si es posible, preferir patrones de lectura y escrita que reduzcan coherencia (p. ej. acumular en variables locales y escribir menos frecuentemente).
- Elección de algoritmo: usar algoritmos lock-free o con menor contención cuando la contienda y el tráfico de coherencia sean críticos.
- Diseño NUMA-aware: colocar datos de forma que los hilos trabajen preferentemente con memoria local para reducir latencias y tráfico entre nodos.
Coherencia fuera del hardware
El concepto de coherencia también existe en sistemas de caché distribuidos (por ejemplo, caches en servidores web o CDN). Allí las soluciones suelen sacrificarse hacia modelos de consistencia más relajados (por ejemplo, eventual consistency), políticas de expiración, invalidaciones explícitas o protocolos de actualización según necesidad, ya que las latencias y la escala son muy diferentes a las de un bus de CPU.
Resumen
La coherencia de caché es esencial en sistemas multiproceso para asegurar que todas las copias de un mismo dato sean consistentes. Se aborda con una combinación de protocolos de hardware (snooping, directorios, MSI/MESI, invalidate/update) y prácticas de software (sincronización, diseño de datos, padding). Cada solución tiene sus ventajas y costes: elegir la adecuada depende de la arquitectura (número de núcleos, topología de interconexión), el patrón de acceso a los datos y los requisitos de rendimiento y consistencia.

