CPU cache
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 trovano 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).
In generale, il grado di associatività della cache rappresenta un compromesso tra prestazioni e complessità. Maggiore associatività significa che ogni dato può essere memorizzato in più posizioni all’interno di un set, riducendo la probabilità di cache miss. Tuttavia, questo comporta anche un maggiore costo in termini di tempo, energia e spazio, poiché il sistema deve controllare più posizioni a ogni accesso. Un principio empirico largamente accettato è che raddoppiare l’associatività (ad esempio passando da una cache direct-mapped a una 2-way o 4-way set associative) produce benefici simili a raddoppiare la dimensione della cache in termini di aumento del tasso di successo (hit rate). Tuttavia, oltre il 4-way, i vantaggi tendono a ridursi, e associatività più elevate vengono usate soprattutto per gestire casi specifici come il virtual aliasing (descritto nella sezione dedicata alla Traduzione degli Indirizzi).
Uno dei vantaggi delle cache a mappatura diretta è la possibilità di eseguire operazioni in modo speculativo in modo semplice e rapido. Una volta calcolato l’indirizzo di memoria, è subito noto quale linea della cache potrebbe contenere il dato richiesto. Questo consente al processore di leggere preventivamente quella linea e iniziare a lavorare sul dato, anche prima di verificare se l’etichetta (tag) memorizzata corrisponda effettivamente all’indirizzo richiesto.
Questo principio può essere esteso anche alle cache associative, sebbene con maggiore complessità. In questi casi, un sottoinsieme dell’etichetta, chiamato comunemente hint, può essere usato per selezionare provvisoriamente una delle linee candidate all’interno del set associato all’indirizzo. La CPU può così iniziare ad utilizzare temporaneamente quel dato, in parallelo alla verifica completa dell’etichetta. Questa tecnica speculativa è particolarmente utile quando viene impiegata insieme ai meccanismi di traduzione degli indirizzi, permettendo alla CPU di anticipare l'accesso ai dati, riducendo i ritardi dovuti alla latenza di memoria.
Cache miss
Con il termine cache miss (in italiano fallimento della cache) si indica una situazione in cui il processore tenta di leggere o scrivere un dato nella cache, ma quel dato non è presente, e deve quindi essere recuperato dalla memoria principale, con un conseguente aumento della latenza. Se la cache miss riguarda una lettura di istruzioni, il processore è costretto ad attendere il caricamento dell’istruzione dalla memoria principale, entrando in uno stato di stallo. Se invece la cache miss avviene durante il caricamento di un dato, il rallentamento può essere meno grave, perché il processore può continuare a eseguire altre istruzioni non dipendenti da quel dato, almeno fino a quando l'operazione che lo richiede diventa eseguibile. Tuttavia, in molti casi, i dati caricati vengono utilizzati immediatamente dopo l’istruzione di caricamento, il che rende comunque l’attesa penalizzante. Le cache miss in scrittura, infine, sono generalmente meno problematiche, perché le scritture vengono bufferizzate: il processore può proseguire l’esecuzione mentre i dati vengono scritti in secondo piano, salvo che il buffer non si riempia. Da notare che non esistono cache miss in scrittura per le cache istruzioni, in quanto le istruzioni sono solitamente di sola lettura e non vengono mai scritte direttamente nella cache da parte del processore.
Per ridurre la frequenza delle cache miss, è stato svolto un ampio lavoro di analisi sul comportamento della cache, con l’obiettivo di identificare la combinazione ottimale di dimensione, associatività, dimensione dei blocchi e altri parametri. Per studiare queste variabili, i ricercatori utilizzano le sequenze di accesso alla memoria prodotte dai programmi di benchmark, salvate sotto forma di tracce di indirizzi (address traces). Su queste tracce vengono simulate diverse configurazioni di cache, per valutare come ciascun parametro influenzi il numero di accessi riusciti e di cache miss.
Tuttavia, comprendere l’effetto combinato di tutte queste variabili può risultare complesso. Un contributo fondamentale è stato fornito da Mark Hill, che ha proposto una classificazione delle cache miss in tre categorie principali, note come le "tre C":
- Compulsory misses (miss obbligate): si verificano alla prima richiesta di un dato mai caricato prima nella cache. Questi fallimenti sono indipendenti dalla dimensione o dall’associatività della cache. Possono essere mitigati con tecniche di prefetching o aumentando la dimensione dei blocchi, che di fatto prelevano più dati in una sola operazione.
- Capacity misses (miss da capacità): avvengono quando la cache non è abbastanza grande da contenere tutti i dati attivamente utilizzati. Questi fallimenti dipendono unicamente dalla dimensione totale della cache, e analizzarne la frequenza in funzione della dimensione permette di valutare la località temporale degli accessi.
- Conflict misses (miss da conflitto): si verificano quando due o più dati che potrebbero coesistere nella cache si contendono la stessa posizione, costringendo la cache a sovrascrivere dati ancora utili. Sono tipici delle cache non completamente associative. Questi possono essere ulteriormente suddivisi in:
- * Mapping misses, dovuti alla struttura stessa della cache (es. direct mapped);
- * Replacement misses, causati dalla scelta della politica di rimpiazzamento, che può espellere un dato che sarebbe stato riutilizzato a breve.
Il grafico a destra riassume la performance della cache vista dai benchmarks della porzione degli interi di un SPEC CPU2000, ripresa da Hill e Cantin [1]. Questi benchmark servono a rappresentare il tipo di carico di lavoro che una postazione di lavoro potrebbe subire un giorno qualsiasi. In questo grafico possiamo vedere i differenti effetti delle tre C.
All'estrema destra, quando la cache size assume un valore "Inf" (che, in altre parole, tende all'infinito), abbiamo i compulsory misses. Se volessimo migliorare le caratteristiche dello SpecInt2000, aumentare la dimensione della cache oltre 1MB sarebbe praticamente inutile.
La frequenza di fallimento in una cache fully-associative (completamente associativa) riflette in modo diretto il numero di capacity misses, cioè i casi in cui la cache non è abbastanza grande per contenere tutti i dati attivamente usati, indipendentemente dalla struttura della cache stessa. Nelle simulazioni, si adotta comunemente una politica di rimpiazzamento LRU (Least Recently Used), che rimuove il dato meno utilizzato di recente. Tuttavia, anche questa strategia non è sempre ottimale. Per ottenere il minimo teorico possibile di capacity misses, sarebbe necessaria una politica di rimpiazzamento perfetta, capace di prevedere con esattezza quali dati non verranno più utilizzati nel prossimo futuro. Una tale previsione, naturalmente, è irrealizzabile in pratica, ma viene spesso evocata come ipotesi ideale — come se un "veggente" potesse scrutare il futuro per fare la scelta perfetta.
Nel grafico che rappresenta la frequenza dei capacity misses, si può osservare una brusca diminuzione tra i 32 KB e i 64 KB di dimensione della cache. Questo comportamento suggerisce che il working set del benchmark — ovvero l’insieme di dati usati attivamente in un dato periodo — sia di circa 64 KB. Un progettista, esaminando questo risultato, potrebbe essere fortemente incentivato a scegliere una dimensione della cache appena superiore a 64 KB, così da ridurre significativamente i fallimenti. Al contrario, impostare la cache appena al di sotto di questo valore comporterebbe un aumento netto della frequenza di cache miss.È inoltre importante notare che, in questa simulazione, nessun livello di associatività rende una cache da 32 KB efficace quanto una cache da 64 KB con associatività 4-way, o una cache direct-mapped da 128 KB. Ciò evidenzia come la dimensione della cache sia spesso più determinante dell’associatività, almeno oltre certi limiti.
Infine, si osserva che tra i 64 KB e 1 MB di dimensione, esiste una notevole differenza di prestazioni tra le cache direct-mapped e quelle fully-associative. Questa differenza è dovuta principalmente alla frequenza dei conflict miss, cioè ai fallimenti causati dal fatto che più dati si contendono la stessa posizione nella cache. Secondo i dati disponibili nei primi anni 2000, le cache di secondo livello (L2) integrate direttamente sul chip del processore tendevano a rientrare proprio in questo intervallo di dimensioni. Le cache più piccole, infatti, sono abbastanza rapide da essere utilizzate come cache di primo livello (L1), mentre quelle molto grandi risultano troppo costose e complesse da integrare fisicamente sul chip. Ad esempio, il processore Itanium 2 montava una cache di terzo livello (L3) da 9 MB, che all’epoca era la più grande cache on-chip disponibile sul mercato. Dal punto di vista dell’efficienza, questi dati mostrano che una cache di secondo livello beneficia notevolmente di una maggiore associatività, in quanto questa riduce sensibilmente i conflitti tra dati e quindi la frequenza di cache miss, migliorando le prestazioni complessive del sistema.
Il vantaggio dell’alta associatività nelle cache era già noto alla fine degli anni ottanta e all’inizio degli anni novanta. Tuttavia, all’epoca i progettisti di CPU incontravano limitazioni fisiche e tecnologiche: non era possibile integrare cache di grandi dimensioni direttamente sul chip, né c’era sufficiente larghezza di banda per gestire una cache altamente associativa posta fuori dal processore. Per superare queste difficoltà, furono esplorate diverse soluzioni architetturali. Il processore MIPS R8000, ad esempio, utilizzava SRAM off-chip dedicate e costose, dotate di comparatori di etichette e driver ad alte prestazioni, per implementare una cache associativa 4-way da 4 MB. Il successivo MIPS R10000, invece, utilizzava SRAM comuni per conservare le etichette, ma richiedeva due cicli di clock per accedere ai dati e verificare l’etichetta, a causa della maggiore complessità. Per ridurre la latenza di accesso, il R10000 introdusse una tecnica di predizione: al momento della richiesta di un dato, cercava di anticipare quale delle vie della cache avrebbe contenuto l’informazione corretta, avviando in parallelo il controllo completo delle etichette. Questa strategia permetteva di accelerare l’accesso nella maggior parte dei casi, nonostante le limitazioni hardware del periodo.
Cache hit
Cache colpita (in inglese cache hit) è il termine usato per indicare il successo del processore nel trovare un dato richiesto direttamente all'interno della memoria cache, senza dover accedere alla memoria principale (RAM), che è più lenta. Quando il dato cercato è presente in cache, si verifica un hit e il processore può accedere rapidamente all'informazione. Questo accesso può riguardare sia la lettura (cache read hit) sia la scrittura (cache write hit) del dato sulla linea di cache corrispondente.
In caso di hit di lettura, il processore legge il dato direttamente dalla cache, evitando qualsiasi coinvolgimento della memoria principale. In caso di hit di scrittura, il processore scrive il dato nella cache secondo regole specifiche, descritte nella politica di scrittura della cache.
Traduzione degli indirizzi
La maggior parte delle CPU comunemente utilizzate implementa un qualche tipo di sistema di memoria virtuale. Questo meccanismo consente a ogni programma in esecuzione di vedere un proprio spazio di memoria separato, che include solo il codice e i dati relativi a quel programma. In questo modo, ciascun programma può operare in modo indipendente, senza doversi preoccupare della memoria utilizzata dagli altri programmi.
Per rendere possibile questa separazione, il processore deve tradurre gli indirizzi virtuali generati dal programma in indirizzi fisici nella memoria principale. Questa traduzione è gestita da una componente del processore chiamata memory management unit (MMU). La MMU può accedere rapidamente alle informazioni di traduzione grazie al Translation Lookaside Buffer (TLB), una cache che memorizza le corrispondenze più recenti della page table gestita dal sistema operativo.
La traduzione degli indirizzi virtuali in indirizzi fisici, svolta dalla MMU, presenta tre caratteristiche fondamentali:
- Latenza: In genere, la MMU fornisce l’indirizzo fisico corrispondente pochi cicli di clock dopo che l’indirizzo virtuale è stato generato dal processore. Questo breve ritardo è necessario per completare la traduzione.
- Aliasing: È possibile che più indirizzi virtuali facciano riferimento allo stesso indirizzo fisico. Per garantire la coerenza dei dati, la maggior parte dei processori assicura che ogni modifica a un indirizzo fisico avvenga in modo ordinato. A tal fine, il processore deve impedire che più copie dello stesso dato fisico siano presenti contemporaneamente in cache.
- Granularità: Lo spazio degli indirizzi virtuali è suddiviso in blocchi chiamati pagine. Ad esempio, uno spazio virtuale di 4 GB può essere suddiviso in 1.048.576 pagine da 4 KB ciascuna, ognuna gestita separatamente. Alcuni sistemi supportano anche pagine di dimensioni variabili; per ulteriori dettagli si veda la voce memoria virtuale.
In origine, i primi sistemi dotati di memoria virtuale risultavano piuttosto lenti, poiché per ogni accesso alla memoria era necessario consultare la page table, che risiedeva nella memoria principale. Questo comportava un doppio accesso alla memoria per ogni operazione, riducendo sensibilmente le prestazioni. Per ovviare a questo limite, fu introdotta la Translation Lookaside Buffer (TLB), una piccola cache specializzata che memorizza temporaneamente le mappature più recenti tra indirizzi virtuali e fisici. Di fatto, la TLB fu la prima cache hardware utilizzata nei computer, ancor prima delle cache dedicate ai dati o alle istruzioni.
La distinzione tra indirizzi virtuali e indirizzi fisici solleva la questione su quali debbano essere utilizzati per gli indici e le etichette della cache. Utilizzare indirizzi virtuali consente una maggiore velocità, perché evita di attendere la traduzione da parte della MMU prima di accedere alla cache. Questo è particolarmente importante per ridurre la latenza di caricamento dalla memoria principale, un fattore critico per le prestazioni della CPU. Per questo motivo, molte cache di primo livello (L1) sono progettate per essere indicizzate usando indirizzi virtuali, permettendo alla MMU di eseguire in parallelo la ricerca nella TLB e l'accesso ai dati presenti nella cache.
L’uso di indirizzi virtuali per l’accesso alla cache non è sempre la soluzione migliore. Una delle principali difficoltà è rappresentata dal fenomeno degli alias virtuali, ovvero la possibilità che uno stesso indirizzo fisico venga associato a più indirizzi virtuali diversi. In questi casi, la cache potrebbe conservare copie duplicate dello stesso dato in posizioni distinte, compromettendo la coerenza tra le informazioni. Gestire correttamente questi alias diventa più complesso man mano che la cache cresce di dimensioni. Per questo motivo, la maggior parte delle cache di secondo livello (L2) e di livello superiore sono progettate per essere indicizzate con indirizzi fisici, eliminando il problema alla radice e garantendo una gestione più efficiente e coerente dei dati in memoria.
L’impiego di indirizzi virtuali per le etichette della cache, noto come virtual tagging, è piuttosto raro. In molti casi, infatti, la ricerca nella TLB si conclude prima dell’accesso completo alla memoria cache, rendendo disponibile l’indirizzo fisico in tempo utile per confrontarlo con le etichette. In queste situazioni, non è necessario utilizzare etichette virtuali. Le cache di grandi dimensioni, che hanno tempi di accesso più lunghi, sono generalmente etichetate con indirizzi fisici (physically tagged) per garantire coerenza e semplicità nella gestione. Al contrario, le cache più piccole e veloci, come quelle di primo livello, possono ancora utilizzare il virtual tagging per ridurre la latenza complessiva. Nelle architetture più recenti, tuttavia, il virtual tagging è stato in parte sostituito da meccanismi più avanzati, come i vhints, descritti più avanti, che consentono una gestione ancora più efficiente dell’indirizzamento e dell’accesso ai dati in cache.
Virtual indexing e virtual aliases
Il processore garantisce la corretta gestione degli alias virtuali ordinandoli in modo tale che, in ogni momento, nella cache possa essere presente una sola copia di un determinato indirizzo fisico corrispondente a uno degli alias virtuali.
Ogni volta che un nuovo dato viene inserito nella cache, il processore verifica se esistono altri alias virtuali corrispondenti e, se li trova, li elimina per evitare duplicazioni. Questa operazione avviene solo in caso di cache miss (mancato reperimento del dato). Durante un cache hit (accesso riuscito), invece, non è necessario alcun controllo aggiuntivo, permettendo così di mantenere il percorso rapido e efficiente all’interno della cache.
Il modo più semplice per individuare gli alias virtuali è far sì che tutti gli indirizzi virtuali che corrispondono allo stesso indirizzo fisico siano mappati nella stessa area della cache. Questo avviene, ad esempio, quando il TLB gestisce pagine di 4 KB e la cache è direct-mapped con una dimensione pari o inferiore a 4 KB.
Le moderne cache di primo livello sono molto più grandi di 4 KB, mentre la dimensione delle pagine di memoria virtuale è rimasta generalmente invariata. Ad esempio, se la cache è di 16 KB e indicizzata con indirizzi virtuali, uno stesso indirizzo fisico può corrispondere a quattro diverse posizioni nella cache, a causa di differenti indirizzi virtuali associati. In caso di cache miss, il processore deve controllare tutte e quattro queste posizioni per verificare se gli indirizzi fisici corrispondono realmente a quello richiesto, prima di procedere con l’accesso alla memoria principale.
I controlli necessari per individuare e gestire gli alias virtuali sono simili a quelli che una cache set-associativa utilizza per trovare una corrispondenza specifica. Per esempio, in una cache indicizzata virtualmente da 16 KB con una configurazione 4-way set-associativa e pagine di memoria virtuale da 4 KB, non è richiesto alcun intervento aggiuntivo per eliminare gli alias virtuali in caso di cache miss. Questo perché i controlli per confrontare gli indirizzi fisici sono già eseguiti durante la ricerca nella cache, evitando così duplicazioni e mantenendo l’efficienza.
Prendendo come esempio un AMD Athlon, questo processore dispone di una cache dati di primo livello da 64 KB, con pagine di memoria virtuale da 4 KB e una configurazione 2-way set associative. Quando la cache dati di primo livello subisce un cache miss, due dei sedici possibili alias virtuali (dato che 64 KB diviso 4 KB fa 16) sono già stati controllati. Per completare l’eliminazione degli eventuali altri alias virtuali, sono necessari circa sette cicli aggiuntivi nel circuito di controllo delle etichette.
Virtual tags and vhints
È possibile utilizzare anche l’etichettatura virtuale (virtual tagging). Il principale vantaggio di questo metodo, soprattutto nelle cache associative, è che permette di confrontare le etichette prima che avvenga la traduzione dall’indirizzo virtuale a quello fisico, accelerando così l’accesso. Tuttavia, ci sono alcune complessità da considerare:
- I controlli di coerenza e la rimozione delle voci nella cache si basano sugli indirizzi fisici. Perciò, l’hardware deve disporre di un meccanismo per convertire l’indirizzo fisico in una posizione della cache. Questo comporta che la cache memorizzi sia le etichette virtuali sia quelle fisiche. Al contrario, una cache con etichettatura fisica (physically tagged) gestisce solo etichette fisiche, semplificando il progetto.
- Quando una traduzione dall’indirizzo virtuale a quello fisico viene rimossa dalla TLB, le informazioni corrispondenti nella cache devono essere invalidate (cioè svuotate o aggiornate). Inoltre, se la cache contiene dati relativi a pagine non più mappate dalla TLB, questi dati devono essere rimossi quando cambiano i permessi di accesso a tali pagine nella page table.
Il sistema operativo può permettere che più alias virtuali dello stesso indirizzo fisico siano presenti contemporaneamente nella cache, tramite una tecnica chiamata page coloring, che sarà descritta più avanti. Alcuni processori RISC, come quelli delle famiglie SPARC e RS/6000, hanno adottato questo approccio. Tuttavia, recentemente questa pratica è stata meno utilizzata, perché il costo hardware per rilevare e rimuovere gli alias virtuali si è ridotto, mentre la complessità e l’impatto sulle prestazioni del software necessario per implementare un page coloring preciso sono aumentati.
È utile distinguere due funzioni svolte dall’etichettatura in una cache associativa:
- La prima serve a scegliere quale linea del set selezionare (modalità di selezione).
- La seconda serve a verificare se l’accesso alla cache è un successo o un fallimento (hit o miss).
- La seconda funzione deve sempre essere accurata, mentre per la prima è possibile accettare qualche errore occasionale (una “predizione” non sempre corretta), senza compromettere il funzionamento complessivo della cache.
Alcuni processori, come quelli recenti della famiglia SPARC, utilizzano cache che combinano sia etichette virtuali sia etichette fisiche. In questo tipo di cache, le etichette virtuali sono impiegate per selezionare il modo (ossia la linea o il blocco da verificare), mentre le etichette fisiche servono a determinare se l’accesso è un successo (cache hit) o un fallimento (cache miss). Questa soluzione unisce il vantaggio di una bassa latenza tipica delle cache con etichette virtuali alla semplicità di gestione del software garantita dalle cache con etichette fisiche. Tuttavia, comporta un costo maggiore dovuto alla necessità di mantenere etichette duplicate.Durante i casi di fallimento della cache, i modi alternativi della linea devono essere controllati per eventuali alias virtuali e, se necessario, le corrispondenze errate devono essere eliminate per mantenere la coerenza.
L’area di memoria aggiuntiva (e la relativa latenza) associata all’uso di etichette virtuali può essere mitigata impiegando dei virtual hints invece delle etichette virtuali complete. Questi hints rappresentano un sottoinsieme, o un hash, dell’etichetta virtuale e vengono utilizzati per selezionare il modo della cache da cui leggere i dati e la relativa etichetta fisica. In una cache con etichette virtuali, può verificarsi una corrispondenza tra virtual hints ma non tra etichette fisiche: in tal caso, l’informazione nella cache associata all’hint coincidente deve essere rimossa, affinché gli accessi successivi allo stesso indirizzo trovino una sola corrispondenza. Poiché i virtual hints contengono meno bit rispetto alle etichette virtuali complete, distinguono meno efficacemente gli indirizzi. Di conseguenza, una cache basata su virtual hints tende a soffrire più frequentemente di mancanze dovute a conflitti (conflict misses) rispetto a una cache con etichette virtuali complete.
Un esempio estremo di riduzione dei virtual hints si osserva nei processori Pentium 4 (core Willamette e Northwood). In questi modelli, l’hint virtuale è ridotto a soli 2 bit, mentre la cache è organizzata come una 4-way set associative. Il processore implementa una semplice permutazione tra gli indirizzi virtuali e gli indirizzi della cache, tale da rendere superfluo l’uso di un Content-Addressable Memory (CAM) per selezionare il modo corretto da cui recuperare i dati. Questo approccio consente una selezione rapida del blocco in cache, pur mantenendo i vantaggi dell’associatività.
Page coloring
Cache indicizzate fisicamente larghe (solitamente cache secondarie) riscontrano un problema: il sistema operativo piuttosto che le applicazioni controlla quali pagine collidono vicendevolmente nella cache. Differenze nell'allocazione delle pagine da un programma portano al prossimo livello di differenze nei percorsi di collisione della cache, i quali possono portare a differenze molto larghe nelle prestazioni dei programmi. Queste differenze possono far diventare molto difficile ottenere un consistente e ripetibile tempo di benchmark per i programmi in esecuzione, che porta ingegneri pagati e sconsolati a richiedere che gli autori del sistema operativo risolvano il problema.
Per capire il problema, consideriamo una CPU con 1MB di cache di livello-2 direct-mapped indicizzata fisicamente e 4KB di pagine di memoria virtuale. Pagine fisiche in sequenza si mappano in posizioni in sequenza nella cache fino a che dopo 256 pagine il percorso torna su se stesso. Possiamo etichettare ogni pagina fisica con un colore da 0-255 per denotare dove nella cache può andare. Posizioni all'interno di pagine fisiche con colori differenti non possono entrare in conflitto nella cache.
Un programmatore che voglia usare al massimo l'uso della cache potrebbe arrangiare i suoi accessi del programma cosicché solo 1MB di data necessiti di essere messo in cache per volta, tutto questo evitando fallimenti di capacità. Ma dovrebbe anche assicurarsi che gli accessi non abbiano fallimenti di conflitto. Un modo per pensare a questo problema è di suddividere le pagine virtuali che utilizza il programma e assegnare a loro colori virtuali nello stesso modo come colori fisici erano assegnati a pagine fisiche precedentemente. Il programmatore può poi arrangiare gli accessi del suo codice in modo che due pagine con lo stesso colore virtuale non siano in uso nello stesso momento. C'è una distesa letteratura su queste ottimizzazioni (per esempio. Loop nest optimization), proveniente soprattutto dalla comunità High Performance Computing (HPC).
Il concetto è che mentre tutte le pagine in uso in un determinato momento, potrebbero avere differenti colori virtuali, alcune potrebbero avere lo stesso colore fisico, Infatti, se il sistema operativo assegna pagine fisiche a pagine virtuali in modo casuale e uniforme, è molto probabile che alcune pagine abbiano lo stesso colore fisico, e quindi posizioni da queste pagine coincidano nella cache (questo è il Birthday paradox).
La soluzione sta nel fare in modo che il sistema operativo tenti di assegnare pagine fisiche colorate diversamente a differenti colori virtuali, una tecnica chiamata page coloring. Sebbene la mappatura attuale da colori virtuali a fisici sia irrilevante per le prestazioni del sistema, mappature dispari sono difficili da tracciare e hanno piccoli benefici, quindi la maggior parte degli approcci alla colorazione delle pagine tenta semplicemente di tenere pagine fisiche e virtuali colorate nello stesso modo.
Se il sistema operativo può garantire che ogni pagina fisica si riferisca a un solo colore virtuale, allora non vi sono virtual alias, e il processore può usare cache virtually indexed senza la necessità di controlli su extra virtual alias durante la gestione del fallimento. Alternativamente il sistema operativo può svuotare una pagina dalla cache quantunque cambi da un colore virtuale a un altro. Come menzionato prima, questo approccio fu usato da qualche recente progettazione SPARC e RC/6000.
Gerarchia delle cache in un processore moderno
I processori moderni dispongono sul chip di cache multiple con cui interagire. Due motivi, in particolare, hanno portato allo sviluppo della attuale gerarchia delle cache.
Cache specializzate
Il primo motivo è che CPU con pipeline accedono alla memoria da molteplici punti nella pipeline: recupero delle istruzioni, traduzione indirizzi da virtuali a fisici, e recupero dei dati. La naturale implementazione è di utilizzare differenti cache fisiche per ognuno di questi punti, cosicché nessuna risorsa fisica debba essere programmata per servire due punti nella pipeline. Sebbene la pipeline finisca naturalmente con almeno tre cache separate (istruzioni, TLB, e data), ognuna è specializzata in un ruolo particolare.
Victim cache
Una victim cache è una cache utilizzata per mantenere blocchi rimossi dalla cache della CPU a causa di un conflict miss o capacity miss. La victim cache è situata tra la cache primaria e la memoria sottostante, e mantiene solamente i blocchi rimossi dopo un miss. Questa tecnica è utilizzata per ridurre la penalità in cui si incorre per un fallimento della cache, perché può accadere che i dati che sono nella victim cache vengano richiesti qualche tempo dopo, e allora invece di dichiarare un miss, e andare in memoria a recuperare questi dati, si controlla la victim cache e si utilizzano i dati che sono ancora dentro di essa.
La victim cache originale su un HP PA7200 fu una cache piccola, fully-associative. Processori posteriori, come gli AMD Athlon, Athlon XP e Athlon 64 utilizzavano la cache secondaria molto grande come una victim cache, per evitare ripetizioni d'immagazzinamenti del contesto sulla cache primaria.
Trace cache
Uno dei più estremi esempi di specializzazione della cache è quello della trace cache utilizzata nei microprocessori Pentium 4. Una trace cache è un meccanismo per aumentare il fetch bandwidth d'istruzioni immagazzinando tracce d'istruzioni che sono già state immagazzinate. Il meccanismo fu per la prima volta proposta da Eric Rotenberg, Steve Bennett e Jim Smith nel loro articolo del 1996: "Trace Cache: a Low Latency Approach to High Bandwidth Instruction Fetching."
Una trace cache immagazzina le istruzioni anche dopo che esse siano state eseguite, o come vengono ritirate. Generalmente, le istruzioni vengono aggiunte alle trace cache in gruppi che rappresentano sia blocchi individuali di base che tracce d'istruzioni dinamiche. Un blocco base consiste in un gruppo d'istruzioni non-branch (Non suddivise) che finiscono con una ramificazione. Una traccia dinamica ("trace path" o "traccia del percorso") consistono nelle solo istruzioni di cui il risultato viene effettivamente utilizzato, ed elimina le istruzioni seguenti che prendono ramificazioni (Siccome non sono eseguite); una traccia dinamica può essere il concatenamento di multipli di blocchi base. Questo permette all'unità di recupero delle istruzioni di recuperare parecchi blocchi basici, senza la preoccupazioni riguardante la ramificazione nel flusso dell'esecuzione.
Le linee di traccia vengono immagazzinate nella trace cache in base al program counter della prima istruzione nella traccia e un set di predizioni di ramificazioni. Questo permette l'immagazzinamento di differenti tracce di percorsi che iniziano con lo stesso indirizzo, ognuna delle quali rappresenta differenti risultati di ramificazione. Nello stage dell'immagazzinamento delle istruzioni di un'Instruction pipeline, il program counter corrente insieme a un set di predizioni di ramificazione viene controllato nella trace cache per un hit. Se un hit avviene, una linea di trace viene fornita per recuperare quale non deve andare in una cache regolare o in memoria per queste istruzioni. La trace cache continua ad alimentare la fetch unit fino a che la linea di traccia finisce o fino a che vi sia una misprediction nella pipeline. Se c'è un fallimento, una nuova traccia inizia a essere creata. Il vantaggio rispetto alle normali cache per il codice è che non vengono mantenute in cache tutte le istruzioni successive a un branch che sia incondizionato o predetto come non seguito: il risultato è che non si formano "bolle" di codice non utilizzato che sprecano spazio di memoria della cache.
Le Trace cache vengono anche impiegate in processori quali l'Intel Pentium 4 per immagazzinare micro operazioni già decodificate, o traduzioni di complesse istruzioni x86, cosicché alla successiva richiesta della stessa istruzione, non debba essere decodificata.
L'idea che sta alla base della trace cache è che nei processori CISC che internamente utilizzano istruzioni RISC, come il Pentium 4, la decodifica delle istruzioni è un'operazione estremamente onerosa, e il suo risultato dovrebbe essere sfruttato al meglio. Utilizzare una trace cache in luogo di una normale cache ha proprio questo vantaggio: non dover decodificare un'istruzione già incontrata durante l'esecuzione di un programma.
Ultimamente la trace cache non gode di molti favori a causa di alcuni difetti. Il primo è che molte istruzioni RISC sono tradotte in una singola istruzione CISC in un solo ciclo di clock, e le istruzioni che necessitano di più cicli di clock per essere tradotte in più istruzioni di tipo RISC sono relativamente poche e poco frequenti, per cui il vantaggio effettivo della trace cache è limitato. A questo si aggiunge il fatto che, nel caso dell'architettura di Intel, le istruzioni di tipo CISC hanno lunghezza variabile in genere tra 1 e 6 byte (tra gli 8 e i 48 bit), mentre tutte le istruzioni RISC utilizzate internamente hanno lunghezza fissa di 118 bit. Quindi a parità di dimensioni una trace cache contiene molte meno istruzioni di una cache normale.
Vedi il testo Smith, Rotenberg and Bennett s paper. URL consultato il 30 novembre 2019 (archiviato dall'url originale il 3 aprile 2008). In Citeseer.
Cache multilivello
Il secondo motivo è il fondamentale compromesso tra la cache latency e lo hit rate. Le cache più grandi sono più lente e hanno migliori hit rate. Per migliorare questo tradeoff, molti sistemi utilizzano livelli multipli di cache, con cache piccole e veloci che si appoggiano a cache più grandi e più lente. Siccome la differenza di latenza tra la memoria principale e le cache più veloci è diventata più grande, alcuni processori hanno cominciato a utilizzare anche tre livelli di cache nel chip. Per esempio nel 2003, Itanium II iniziò a essere fornito con una cache sul chip unificata di livello 3 di 6MB. L'IBM Power 4 series ha una cache di livello 3 a 256 MB fuori dal chip, condivisa attraverso parecchi processori.
Le cache multilivello generalmente operano controllando dapprima le cache a livello 1; se avviene un hit, il processore procede ad alta velocità. Se la cache più piccola “fallisce”, allora viene controllata quella più grande e così via, fino a dover accedere alla memoria principale.
Le cache multi livello introducono un nuovo modello decisionale. Per esempio, in alcuni processori (come gli Intel Pentium 2, 3, e 4, così come in molti RISC), i dati nella cache L1 possono essere anche in quella L2. Queste cache vengono denominato inclusive. Altri processori (come l'AMD Athlon) hanno cache exclusive in cui è garantito che i dati siano al massimo in una delle cache L1 o L2.
Il vantaggio delle cache esclusive è che possono memorizzare più dati. Questo vantaggio diventa più evidente con cache di dimensioni maggiori, sebbene questa caratteristica non sia presente nelle implementazioni Intel x86. D'altra parte, un vantaggio delle cache inclusive è che quando dispositivi esterni o altri processori in un sistema multiprocessore vogliono rimuovere una linea di cache dal processore, devono far controllare al processore solo la cache L2. Nelle gerarchie di cache che non utilizzano l'inclusione, è necessario controllare anche le cache L1. Inoltre, esiste una correlazione tra l'associatività delle cache L1 e L2: se le cache L2 non hanno almeno tanti modi quanti le cache L1 complessivamente, l'effettiva associatività delle cache L1 risulta limitata.
Un altro vantaggio delle cache inclusive è che le cache più grandi possono usare linee di cache più grandi, che riducono la dimensione delle etichette delle cache secondarie. Se la cache secondaria è di un ordine di grandezza maggiore di quella primaria, e i dati della cache sono di un ordine di grandezza più grande delle etichette della cache, queste etichette di dati salvati può essere confrontato con l'area incrementale necessaria a immagazzinare i dati nella cache L1 ed L2.
Come menzionato prima, grandi computer hanno a volte un'altra cache tra quella L2 e la memoria principale chiamata cache L3. Questa cache è implementata generalmente su un chip separato dalla CPU, e come nel 2004, ha una capacità dai 2MB ai 256 MB. Queste cache costeranno ben oltre i $1000 da costruire, e i loro benefici dipenderanno dai percorsi di accesso delle applicazioni. Workstation x86 di fascia alta e server sono ora disponibili con un'opzione per la cache L3.
Infine, dall'altro lato della gerarchia della memoria, il Register file della CPU può essere considerato la più piccola, veloce cache nel sistema, con la speciale caratteristica che viene richiamata dal software—tipicamente da un compilatore, siccome alloca registri che devono mantenere valori recuperati dalla memoria principale.
Esempio: architettura AMD K8
Per illustrare sia la specializzazione che il multilivello delle cache, qui è la gerarchia della cache di un AMD Athlon 64, la cui implementazione del core è conosciuta come architettura K8. La K8 ha 4 cache specializzate: una cache d'istruzioni, una d'istruzioni TLB, una cache di dati e una di dati TLB. Ognuna di queste cache è specializzata:
- La cache d'istruzione mantiene copie di line di memoria da 64 bytes, e recupera 16 bytes per ogni ciclo. Ogni byte in questa cache è immagazzinato in 10 bit piuttosto che 8, con gli extra bit che segnano i limiti delle istruzioni (Questo è un esempio del precoding). La cache ha solamente una protezione di parità piuttosto che una ECC, perché la parità è più piccola, e ogni dato danneggiato può essere sostituito da un dato fresco dalla memoria (che ha sempre una copia aggiornata delle istruzioni).
- La TLB d'istruzioni tiene copia delle informazioni nella page table (PTE). Ogni ciclo di recupero d'istruzione ha il suo indirizzo virtuale tradotto attraverso questo TLB in uno fisico. Ogni informazione è sia da 4 che da 8 byte in memoria. Ogni TLB è suddivisa in due sezioni, una per mantenere il PTE che mappa 4KB, e una per tenere i PTE per mappare 4MB o 2MB. La suddivisione permette un circuito semplice per un confronto fully associative in ogni sezione. Il sistema operativo mappa sezioni differenti dello spazio d'indirizzi virtuali con differenti dimensioni di PTE.
- La TLB dei dati ha due differenti copie che mantengono le stesse informazioni. Le due copie permettono due accessi ai dati per ogni ciclo per tradurre indirizzi virtuali in fisici. Come la TLB d'istruzioni, questa TLB è suddivisa in due tipi d'informazioni.
- La cache dei dati mantiene copie di memoria di linee da 64 bytes. È suddivisa in 8 banchi (ognuno immagazzina 8KB di dati), e può recuperare due dati da 8-byte per ogni ciclo per tanto che questi dati siano in banchi diversi. Vi sono due copie delle etichette, perché ogni line da 64 byte è sparsa in tutti gli 8 banchi. Ogni copia di etichetta gestisce uno dei due accessi per ciclo.
La K8 ha anche cache a multilivello. Vi sono TLB d'istruzioni e dati di secondo livello, che immagazzinano solo mappature di PTE da 4KB. Sia le cache d'istruzioni che di dati e le varie TLB, possono essere riempite dalla grande cache unified di livello 2. Questa cache è esclusiva per entrambe le cache L1 di dati e istruzioni, il che significa che qualsiasi line a 8-byte può risiedere in una delle cache d'istruzioni L1, cache di dati L1 o cache L2. È comunque possibile per una linea nella cache dei dati di avere un PTE che sta anche in una delle cache TLB—il sistema operativo è responsabile di tenere le TLB coerenti scaricandone porzioni quando la page table nella memoria vengono aggiornate.
La K8 memorizza informazioni che non vengono mai immagazzinate in memoria—prediction information. Queste cache non vengono visualizzate nel diagramma precedente. Siccome è solito per questa classe di CPU, la K8 ha un branch prediction abbastanza complesso, con tabelle che aiutano a predire quali percorsi vengono presi e altre tabelle che predicono gli obbiettivi dei percorsi e salti. Alcune di queste informazioni vengono associate con delle istruzioni, sia nella cache d'istruzioni L1 sia in quella unified L2.
La K8 utilizza un interessante meccanismo per immagazzinare le informazioni di predizione con le istruzioni nella cache secondaria. Le linee nella cache secondaria sono protette dalla corruzione dei dati involontaria (ad esempio un colpo di particelle alfa tramite l'ECC o tramite la parità, in dipendenza che queste linee siano state rimosse dalla cache dei dati o da quella delle istruzioni. Siccome il controllo di parità occupa meno bit di quello ECC, le linee dalla cache d'istruzioni hanno pochi bit in avanzo. Questi bits vengono usati dal calcolo delle predizioni dei percorsi associati con quelli delle istruzioni. Il risultato finale è che il predittore di percorsi ha un grande tabella storica, quindi ha una migliore accuratezza.
Altre gerarchie
Altri processori hanno altri tipi di predittori. (ad esempio lo store-to-load bypass predictor nel DEC Alpha 21264), e altri svariati predittori specializzati sono facilmente pensabili da essere integrati nei futuri processori.
Questi predittori sono cache nel senso che immagazzinano informazioni che sono costose da calcolare. Alcune delle terminologie utilizzate nel discutere i predittori sono le stesse di quelle per le cache (si parla di hit nel predittore di percorsi), ma i predittori non sono generalmente pesati come parte della gerarchia delle cache.
La K8 mantiene le istruzioni e i dati delle cache coerenti nell'hardware, il che significa che un immagazzinamento in un'istruzione appena dopo l'immagazzinamento dell'istruzione cambierà l'istruzione seguente. Altri processori, come quelli nella famiglia Alpha e MPS, sono basati sul software per mantenere le cache d'istruzioni coerenti. Gli immagazzinamenti non sono garantiti di essere visti nel fiume d'istruzioni a meno che un programma chiami un'opzione del sistema operativo per assicurarsi della coerenza. L'idea è quella di risparmiare la complessità dell'hardware sull'assunzione che il codice automodificante è raro.
La gerarchia di cache si allarga se consideriamo il software come l'hardware. Il register file nel core di un processore può essere considerata una cache molto piccola, veloce i quali hit, fallimenti, e riempimenti sono previsti dal compilatore prima del tempo (vedi specialmente Loop nest optimization). I register file a volte hanno anch'essi una gerarchia: la Cray-1 (circa del 1976) aveva 8 registri scalari e 8 registri d'indirizzi che erano generalmente utilizzabili, aveva anche 64 registri scalari e 64 registri d'indirizzi di tipo "B". I registri di tipo "B" potevano essere caricati più velocemente di quelli nella memoria principale, in quanto il Cray-1 non aveva una cache di dati.
Implementazione
Dato che le letture dalla cache sono operazioni comuni che richiedono più cicli di clock, il percorso critico nel design dei processori è spesso il trasferimento di dati dalla cache a un'istruzione. Questo percorso è essenziale per ottimizzare le prestazioni della CPU, poiché un accesso veloce alla cache L1 è fondamentale per ridurre i ritardi. La cache più semplice è una cache direttamente mappata, dove l'indirizzo virtuale è calcolato da un sommatore e utilizzato per indicizzare una SRAM (Static Random Access Memory), dalla quale vengono letti i dati corrispondenti. Non è necessario alcun controllo di etichetta nel ciclo interno, poiché le etichette non devono essere lette fino a quando non viene verificato un cache hit. Se la verifica fallisce, la cache viene aggiornata e il processo riparte.
Le cache associative sono più complesse, poiché richiedono la lettura di alcuni dati per determinare quale punto della cache selezionare. Ad esempio, una cache level-1 set-associative N-way legge tutte le possibili etichette e dati in parallelo e seleziona quelli associati all'etichetta corrispondente. Le cache level-2 possono risparmiare potenza leggendo prima le etichette e selezionando quindi i dati dalla SRAM.
Il diagramma a destra illustra l'organizzazione degli indirizzi nella cache. Ad esempio, una cache 4KB, 2-way set-associative con linee da 64B utilizza 64 linee e legge due alla volta da una SRAM con 32 righe. Sebbene qualsiasi funzione degli indirizzi virtuali da 31 a 6 possa indicizzare etichette e dati SRAM, è più comune utilizzare i bit meno significativi.
Inoltre, una cache più moderna potrebbe essere di dimensioni diverse e utilizzare diverse tecniche, come indicizzazione virtuale, suggerimenti virtuali e indicizzazione fisica. Queste cache seguono un percorso di lettura simile, ma utilizzano diversi tipi di dati, come vhints anziché etichette, per determinare un cache hit.
Alcune implementazioni SPARC hanno migliorato le prestazioni delle loro cache L1 riducendo i ritardi del sommatore dell'indirizzo virtuale nei decoder SRAM.
Note
Voci correlate
Altri progetti
- Wikibooks contiene testi o manuali sulla CPU cache
- Wikimedia Commons contiene immagini o altri file sulla CPU cache
Collegamenti esterni
- (EN) Evaluating Associativity in CPU Caches — Hill and Smith — 1989 — Introduces capacity, conflict, and compulsory classification.
- (EN) Cache Performance for SPEC CPU2000 Benchmarks. — Hill and Cantin — 2003 — This reference paper has been updated several times. It has thorough and lucidly presented simulation results for a reasonably wide set of benchmarks and cache organizations.
- (EN) Memory Hierarchy in Cache-Based Systems (PDF) (archiviato dall'url originale il 15 settembre 2009)., by Ruud van der Pas, 2002, Sun Microsystems, is a nice introductory article to CPU memory caching.
- (EN) A Cache Primer (PDF) (archiviato dall'url originale il 25 luglio 2008). by Paul Genua, P.E., 2004, Freescale Semiconductor, another introductory article.