La Cache è un tipo di memoria RAM di piccole dimensioni, progettata per essere molto veloce ma anche più costosa rispetto ad altri tipi di memoria. Con l’aumento delle prestazioni dei processori e l’uso crescente di tecnologie avanzate come l’intelligenza artificiale e il cloud computing, la cache continua a giocare un ruolo fondamentale. Quando si parla di "lato prestazioni", si intende che la cache aiuta il computer o il dispositivo a lavorare più rapidamente: conserva temporaneamente le informazioni e i dati usati più spesso, permettendo al processore di accedervi immediatamente senza doverli recuperare da memorie più lente o da internet. Questo rende le operazioni più veloci, migliora l’efficienza energetica e garantisce una migliore esperienza d’uso, anche con applicazioni moderne e complesse.

Caratteristiche

Nel diagramma a sinistra sono illustrate due memorie: la memoria principale e la cache. La cache è una memoria molto più veloce, ma anche più piccola, usata per conservare temporaneamente i dati più usati dal processore.

Ogni posizione della cache contiene un dato chiamato linea di cache (o blocco di cache), che attualmente può variare da 512 byte a 8 MB, a seconda del tipo e livello di cache. Al contrario, la memoria principale ha dimensioni molto più grandi, in genere tra 1 e 16 GB o più, ma è anche più lenta.

Ogni posizione in memoria è identificata da un numero univoco chiamato indice (o indirizzo di memoria). Nella cache, a ogni linea è associata un’etichetta (o tag), che indica a quale indirizzo della memoria principale corrisponde quel dato.

Quando il processore ha bisogno di un'informazione, controlla prima se si trova nella cache. Se la trova (si parla di cache hit), l’accesso è molto veloce. Se invece non è presente (si parla di cache miss), il dato viene preso dalla memoria principale, che è più lenta, e copiato nella cache per usi futuri.

Finché la maggior parte dei dati richiesti si trova nella cache, il tempo medio per accedere alla memoria (latenza) resta basso, vicino a quello rapidissimo della cache. Questo si traduce in prestazioni migliori per il sistema. Quando il processore necessita di leggere o scrivere in una data collocazione in memoria principale, inizialmente controlla se il contenuto di questa posizione è caricato in cache.

Questa operazione viene effettuata confrontando l'indirizzo della posizione di memoria con tutte le etichette nella cache che potrebbero contenere il dato a quell'indirizzo. Se il processore trova che la posizione di memoria è in cache, si parla di cache hit (accesso avvenuto con successo), altrimenti di cache miss (fallimento dell'accesso).

Nel caso di un cache hit, il processore legge o scrive immediatamente il dato sulla linea di cache. Il rapporto tra cache hit e accessi totali è chiamato anche hit rate ed è una misura indiretta dell'efficacia dell'algoritmo di cache.

Quando si verifica una cache miss — cioè il processore richiede un dato assente dalla cache — viene creata una nuova voce che contiene l’etichetta (o tag) e una copia del dato recuperata dalla memoria principale. L’operazione è più lenta rispetto all’accesso diretto alla cache, perché richiede il trasferimento del dato e, spesso, la sostituzione di un dato ormai inutile.

Per decidere quale dato eliminare per far posto al nuovo, la cache adotta una regola detta euristica di rimpiazzamento: si tratta di un metodo approssimato per cercare di prevedere quali dati saranno meno utili in futuro. Tuttavia, anche nel 2025, nessuna euristica è perfetta, soprattutto perché le cache hardware devono basarsi su meccanismi semplici da implementare nei circuiti elettronici. Per esempio, una delle politiche più diffuse è la LRU, che rimuove il dato utilizzato meno recentemente, con l’idea che sia il meno probabile da riutilizzare a breve.

Proprio per questo motivo, una cache molto capiente, anche se controllata da un algoritmo sofisticato, può risultare controproducente in alcune situazioni. Ad esempio, quando il processore lavora con dati che cambiano spesso (come in calcoli non ripetitivi), la cache può riempirsi di informazioni che non verranno più utilizzate.

Ogni nuovo dato deve quindi essere caricato e gestito con l’ulteriore costo di rimuovere dati obsoleti, e questo può rallentare il sistema. In certi casi, il tempo complessivo impiegato per questo processo può essere addirittura superiore a quello richiesto per leggere direttamente il dato dalla memoria principale.

In altre parole, anche oggi una cache più grande non garantisce sempre prestazioni migliori: in scenari particolari può generare più cache miss che cache hit, con un impatto negativo sulla velocità complessiva del sistema.

Alcuni dettagli operativi Quando si verifica una cache miss — ovvero quando il processore richiede un dato non presente nella cache — è necessario liberare spazio per poter memorizzare il nuovo dato. Questo comporta l’eliminazione di uno dei dati già presenti nella cache. La scelta di quale dato rimuovere è guidata da una regola detta politica di rimpiazzamento (replacement policy).

Lo scopo di questa politica è stimare quale dato sia meno probabile che venga riutilizzato a breve, così da poterlo sostituire senza compromettere le prestazioni complessive del sistema. Tuttavia, questa previsione è intrinsecamente complessa, in particolare per le cache hardware, che devono adottare strategie semplici, rapide e facilmente implementabili a livello di circuito.

Sono state sviluppate diverse politiche di rimpiazzamento, ciascuna con punti di forza e debolezze, ma nessuna in grado di garantire risultati ottimali in ogni contesto operativo. Una delle più diffuse è la LRU (Least Recently Used), che rimuove il dato utilizzato meno di recente, basandosi sull’ipotesi che un dato non usato da tempo difficilmente verrà richiesto di nuovo a breve. Pur non essendo ideale in ogni scenario, questa soluzione rappresenta un buon compromesso tra semplicità ed efficacia.

Quando un dato viene modificato nella cache, deve essere prima o poi aggiornato anche nella memoria principale, per mantenere la coerenza tra i due livelli di memoria. Il momento in cui avviene questa operazione dipende dalla cosiddetta politica di scrittura (write policy). In una cache di tipo write-through, ogni modifica effettuata nella cache viene immediatamente replicata anche nella memoria principale. Questo garantisce una maggiore coerenza tra i dati, ma può rallentare le prestazioni a causa dell’elevato numero di accessi alla memoria. Al contrario, in una cache write-back, la scrittura nella memoria principale non avviene subito. I dati modificati restano temporaneamente nella cache e vengono segnati come "sporchi" tramite un apposito indicatore, chiamato dirty bit. Solo quando quella specifica linea di cache deve essere sostituita — ad esempio per far posto a nuovi dati — il contenuto aggiornato viene scritto nella memoria principale. Questo approccio riduce il numero di scritture, migliorando l’efficienza, ma richiede un meccanismo più complesso per garantire la coerenza. In un sistema write-back, una cache miss può generare due accessi alla memoria: uno per scrivere il dato modificato (se segnalato dal dirty bit) e uno per leggere il nuovo dato richiesto.

Esistono anche politiche ibride, che cercano un compromesso tra efficienza e coerenza. Un esempio è una cache write-through con buffer di scrittura, dove le scritture vengono inserite temporaneamente in una coda, per poi essere inviate alla memoria principale in modo più efficiente, ad esempio raggruppandole o gestendole in momenti di basso traffico sul bus di sistema.

In un sistema multiprocessore, è possibile che i dati contenuti nella memoria principale, di cui esiste una copia nella cache, vengano modificati da altri processori. In questi casi, le copie locali nella cache possono diventare obsolete o incoerenti. Per evitare errori di calcolo, i sistemi devono garantire che tutte le cache coinvolte lavorino con dati aggiornati. A tal fine vengono utilizzati appositi protocolli di coerenza, che regolano la comunicazione tra le cache per mantenere l’allineamento corretto dei dati.

Un altro aspetto critico è il tempo necessario per leggere un dato dalla memoria, noto come latenza di lettura. Questo tempo influisce direttamente sulle prestazioni del processore: se la CPU deve attendere troppo a lungo l’arrivo di un dato, rischia di fermarsi temporaneamente, entrando in uno stato chiamato stallo della CPU. Tale situazione è particolarmente dannosa nelle CPU moderne, capaci di eseguire centinaia di istruzioni nel tempo impiegato per caricare un singolo dato da una memoria lenta. Per ridurre l’impatto degli stalli, sono state sviluppate tecniche di ottimizzazione che permettono alla CPU di sfruttare il tempo d’attesa. Ad esempio, alcuni microprocessori adottano l’esecuzione fuori ordine (out-of-order execution), che consente di portare avanti operazioni successive a quella in attesa, purché siano indipendenti dal dato mancante. Altri processori implementano il multithreading simultaneo, come nel caso della tecnologia HyperThreading di Intel, che consente a un secondo flusso di istruzioni (thread) di utilizzare l’unità di calcolo mentre il primo è in attesa dei dati. In questo modo, si riduce lo spreco di cicli di CPU, migliorando l’efficienza del sistema.

Associatività

La politica di rimpiazzamento determina dove, nella cache, può essere memorizzata una copia di un dato della memoria principale. Se il dato può essere inserito in qualsiasi linea, la cache è detta fully associative. Se invece può essere collocato solo in una linea specifica, si parla di cache direct mapped. Un compromesso tra i due approcci è la cache set associative, dove ogni dato può essere inserito in una delle linee di un gruppo (set). Ad esempio, la cache dati di primo livello dell’Athlon di AMD è 2-way set associative, cioè ogni dato può risiedere in due linee distinte del set corrispondente.

Se ogni posizione della memoria principale può essere memorizzata in due linee diverse della cache (come nel caso di una cache 2-way set associative), è naturale chiedersi quali siano queste due posizioni. Il metodo più comune per stabilirlo è quello illustrato nel diagramma a lato: i bit meno significativi dell’indirizzo di memoria vengono usati per determinare a quale set appartiene il dato. A ciascun set corrispondono due linee di cache disponibili. Un vantaggio di questo schema è che, conoscendo già parte dell’indirizzo (quella usata per selezionare il set), le etichette (tag) salvate in cache possono essere più brevi, poiché non devono ripetere l’informazione già codificata. Questo comporta un uso più efficiente della memoria cache e una riduzione del tempo necessario per confrontare i tag durante l’accesso.

Sono stati proposti schemi alternativi di organizzazione della cache per ridurre le collisioni, cioè i casi in cui più dati competono per la stessa posizione. Un esempio è la skewed cache, in cui il metodo di selezione del set varia tra le diverse vie (ways). Nella way 0, l’indice è calcolato con il metodo tradizionale (ad esempio usando i bit meno significativi dell’indirizzo), mentre nella way 1 l’indice è ottenuto tramite una funzione di hash. Una buona funzione di hash distribuisce in modo più uniforme gli indirizzi, riducendo il rischio che dati frequentemente usati entrino in conflitto tra loro, come può accadere con la mappatura diretta.Lo svantaggio di questo approccio è il ritardo aggiuntivo dovuto al calcolo della funzione di hash. Inoltre, può diventare più complesso gestire l’eliminazione delle linee obsolete, perché le diverse vie possono usare criteri di assegnazione diversi, rendendo meno efficiente il tracciamento dell’utilizzo recente (ad esempio nel caso di strategie come la Least Recently Used, o LRU).