Una cache può essere utilizzata per migliorare le prestazioni di accesso a una data risorsa. Quando ci sono diverse cache per la stessa risorsa, come mostrato nell'immagine, questo può portare a problemi. La coerenza della cache o Cache coherency si riferisce a una serie di modi per assicurarsi che tutte le cache della risorsa abbiano gli stessi dati, e che i dati nelle cache abbiano senso (chiamata integrità dei dati). La coerenza della cache è un caso speciale della coerenza della memoria.

Ci possono essere problemi se ci sono molte cache di una risorsa di memoria comune, poiché i dati nella cache potrebbero non avere più senso, o una cache potrebbe non avere più gli stessi dati delle altre. Un caso comune in cui il problema si verifica è la cache delle CPU in un sistema multiprocessing. Come si può vedere nella figura, se il client superiore ha una copia di un blocco di memoria da una lettura precedente e il client inferiore cambia quel blocco di memoria, il client superiore potrebbe essere lasciato con una cache di memoria non valida, senza saperlo. La coerenza della cache è lì per gestire tali conflitti e mantenere la coerenza tra cache e memoria.

Cause dei problemi di incoerenza

  • Più processori o core mantengono copie locali della stessa locazione di memoria per ridurre la latenza degli accessi.
  • Un core può aggiornare la propria copia senza aggiornare immediatamente le altre copie presenti nelle cache degli altri core.
  • Operazioni di scrittura non coordinate possono lasciare copie diverse in stati diversi (valide, invalide, sporche), portando a letture errate o a risultati non deterministici.

Principi di base e obiettivi

  • Assicurare che, per ogni locazione di memoria condivisa, tutte le cache vedano aggiornamenti in modo coerente secondo le regole definite dal protocollo di coerenza.
  • Mantenere l’integrità dei dati, cioè evitare che letture successive restituiscano valori obsoleti non riconciliati con le scritture effettuate da altri processor.
  • Bilanciare correttezza e prestazioni: i meccanismi devono introdurre il minimo overhead possibile in termini di traffico e latenza.

Meccanismi principali

I meccanismi usati nei sistemi reali ricadono in due grandi famiglie.

  • Snooping (o bus-based): ogni cache “annusa” il bus di memoria e osserva le transazioni fatte dagli altri soggetti. Se una cache vede una scrittura sulla locazione che ha in copia, può invalidare o aggiornare la sua copia. Protocollo classico: MESI (Modified, Exclusive, Shared, Invalid).
  • Directory-based: una struttura centralizzata o distribuita (directory) tiene traccia di quali cache possiedono copie di ogni blocco di memoria. Le richieste di lettura/scrittura consultano la directory, che coordina invalidazioni o aggiornamenti. È più scalabile dei sistemi basati su snooping quando ci sono molti core.

Strategie di sincronizzazione dei dati

  • Write-through: ogni scrittura nella cache viene immediatamente propagata alla memoria principale. Semplice ma genera molto traffico di memoria.
  • Write-back: le scritture vengono effettuate solo nella cache e il blocco diventa “sporco” (dirty); la scrittura in memoria avviene solo quando il blocco deve essere rimosso dalla cache. Riduce il traffico ma richiede meccanismi per informare le altre cache (invalida/aggiorna).
  • Invalidazione vs Update:
    • Invalidazione: quando un core scrive, le copie nelle altre cache vengono invalidate; i lettori successivi devono rileggere dalla memoria o dalla cache del writer.
    • Aggiornamento: la scrittura viene propagata alle altre cache che aggiornano la propria copia. Riduce miss di lettura successive ma aumenta il traffico di scrittura.

Protocollo MESI (esempio)

Il protocollo MESI è uno dei più diffusi nelle CPU moderne. Ogni blocco di cache può essere in uno dei quattro stati:

  • Modified: la copia nella cache è diversa dalla memoria principale (è sporca) e nessun'altra cache ha la stessa copia.
  • Exclusive: la cache ha una copia pulita esclusiva (uguale alla memoria) e nessun altro la possiede.
  • Shared: la copia è pulita e può essere presente anche in altre cache.
  • Invalid: la copia non è valida.
Questi stati e le transizioni fra di essi governano come vengono gestite letture e scritture, e come vengono inviate invalidazioni o richieste di fornitura del dato ad altre cache o alla memoria.

Problemi correlati e ottimizzazioni

  • False sharing: quando due variabili diverse, usate da thread diversi, si trovano nello stesso blocco di cache; scritture indipendenti provocano invalidazioni/incoerenze e degradano le prestazioni. La soluzione è spesso a livello di layout dei dati o padding.
  • Ordinamento della memoria: la coerenza della cache non garantisce automaticamente l’ordine visibile delle operazioni a livello di programma; per questo esistono modelli di memoria (strong/weak ordering) e primitive come memory fences/barriers che i compilatori e l’hardware devono rispettare per sincronizzazione corretta.
  • Scalabilità: i protocolli basati su snooping scalano male su centinaia o migliaia di core per via del traffico sul bus; le directory distribuite o le gerarchie di cache sono utilizzate per sistemi molto grandi.

Implicazioni pratiche per programmatori

  • Usare primitive di sincronizzazione (mutex, atomic, barrier) per evitare condizioni di race; non fare affidamento sul comportamento “magico” delle cache.
  • Attenzione al layout della memoria per ridurre il false sharing e migliorare la locality.
  • Conoscere il modello di memoria della piattaforma (x86, ARM, ecc.) aiuta a scrivere codice concorrente corretto ed efficiente.

Conclusione

La coerenza della cache è fondamentale nei sistemi multiprocessore per garantire che i dati condivisi rimangano consistenti e affidabili. Esistono diversi meccanismi hardware e software per ottenere la coerenza, ciascuno con compromessi tra correttezza, prestazioni e scalabilità. Comprendere i principi (invalidazione vs aggiornamento, write-back vs write-through, protocolli come MESI, e problemi come il false sharing) è essenziale per progettare sia l’hardware che il software in ambienti concorrenti.