La cache: che cosa è, come funziona e perché è così importante

La cache: che cosa è, come funziona e perché è così
importante
Molto spesso capita, parlando di personal computers, di sentire dei termini apparentemente
misteriosi, dei quali si conosce il significato ma si ignorano probabilmente tutta una serie di aspetti
importante per avere una comprensione di come un personal computer moderno lavora. Uno di
questi termini è senza dubbio cache (dal francese cacher, nascondere). Cominciamo con una
considerazione: storicamente le CPU sono sempre state più veloci delle memorie. Questo è dovuto
al fatto che il grosso progresso dell’hardware che normalmente utilizziamo è dovuto in buona parte
alla capacità strettamente ingegneristica di integrare sullo stesso chip un numero sempre maggiore
di circuiti. Così facendo si riescono ad aumentare sostanzialmente le prestazioni di una CPU, grazie
al fatto che meglio si frutta la pipeling e la scalabilità, mentre per quanto riguarda le memorie
tendenzialmente si aumenta la capacità più che la velocità. Pare quindi che non ci sia soluzione, e
pare anche che siamo destinati a non sfruttare le nostre CPU di ultima generazione a causa della
lentezza latente delle memorie (centrali) a causa del fatto che la CPU in questo schema dovrebbe
attendere molti cicli di clock (in realtà molti cicli di CPU, ma in questa sede useremo la parola
clock per semplicità). Il problema però è da considerarsi alla luce di considerazioni assolutamente
economiche e non solo tecnologiche. Al giorno d’oggi siamo tranquillamente in grado di produrre
delle memorie veloci quanto una CPU moderna, ma queste memorie hanno un costo elevatissimo.
Oltretutto per avere queste prestazioni le memorie dovrebbero essere piazzata in gran parte sugli
stessi chip delle CPU, il che non è possibile. Per ovviare a questo problema, che ripetiamo essere di
natura strettamente economica, gli ingegneri ricorrono ad uno schema chiamato “a gerarchia di
memoria” in cui si combinano una quantità molto piccola di memoria estremamente veloce (la
cache) e una quantità molto grande di memoria lenta (la memoria centrale, per intenerci la vostra
RAM). Unitamente agli altri tipi principali di memoria, questo schema da luogo ad una gerarchia
come la seguente
PDF Creator - PDF4Free v2.0
http://www.pdf4free.com
Guardando la figura vengono in mente immediatamente alcune considerazioni: il livello più alto di
memoria sono i “registri” interni alla CPU, come si spiega questo fatto? Che differenza c’è tra
questi registri e la cache più interna al processore? Si assume come convenzione che un registro non
sia in grado di contenere al suo interno un valore più grande di un certo numero di bit, noto a priori.
Al contrario, in cache i valori memorizzati sono più di uno, ma si tratta anche in questo caso di un
numero noto a priori. Nei registri delle nostre CPU trovano posto i riferimenti elementari utili al
processore (all’ALU per essere precisi, Arithmetic Logic Unit) che transitano ad un livello diverso
rispetto ai dati immagazzinati in memoria centrale. Per capire questo concetto è utile comprendere
che una CPU non è in grado di eseguire le operazioni così come le intendiamo noi, ma esegue delle
operazioni elementari, il cui numero è definito a priori dal progettista del processore. Non
entreremo in questa sede in dettaglio fino a comprendere come si definiscono queste operazioni, né
ci occuperemo delle differenze tra CISC e RISC, in questo momento basti sapere che possiamo
considerare la cache come un’estensione della memoria centrale mentre i registri come le unità di
memorizzazione interne alla CPU atte a contenere solo ed esclusivamente dati elementari.
A che cosa serve a questo punto la cache? L’idea di fondo è semplice, e si basa su un’osservazione
quasi banale. Assumendo di riuscire a tenere in una memoria particolarmente veloce le parole di
memoria più usate, si incrementa notevolmente la velocità di risposta della macchina. Questo è
esattamente ciò che si fa: si utilizza una memoria statica di dimensioni molto limitate (perché
estremamente costosa) ma con tempi di accessi molto ridotti (fino all’ordine di un solo ciclo di
clock) per contenere le parole di memoria usate più di frequente e quando la CPU richiede una certa
parola prima di andarla a ricercare in memoria centrale si verifica se essa è presente nella cache. Si
distinguono i diversi livelli di cache per aumentare ancora di più la scalabilità:
L1: cache interna al processore, solitamente sincrona con il processore, spesso distinta per dati e
istruzioni (split cache)
L2: generalmente unificata per dati e istruzioni, può essere interna o esterna
L3: generalmente unificata per dati e istruzioni, può essere o meno presente ed è interna o esterna
Il principio tramite il quale si sceglie cosa mettere in cache è noto col nome di “principio di
località” che asserisce che se viene indirizzata una parola, supponiamo all’indirizzo di memoria A, è
molto probabile che le successive e precedenti richieste (in ordine cronologico) siano riferite a
locazioni di memoria vicine ad A. Si trasferiscono quindi degli interi blocchi di memoria centrale in
cache, e si valuta il tempo di accesso medio. Se le parole in essi contenuti sono lette n volte,
avremmo un solo accesso lento in memoria centrale e n-1 accessi veloci in cache; vien da se che
maggiore è n, migliori sono le prestazioni ed il compromesso che abbiamo ottenuto. Per nostra
fortuna i progettisti, grazie al già citato principio di località, riescono a rendere questo valore n
sufficientemente grande. Di seguito diamo alcune specifiche relative alla quantità di cache che
riguardano CPU attualmente in commercio:
Intel PIV HT Extreme edition:
L3: 2MB
L2: 512KB
L1: 8KB
Intel Itanium 2:
L1: 16 KB dati (interi) e 16 KB istruzioni accessibili in 1 ciclo di clock
L2: 256 KB unificata, interna accessibile in minimo 5, 6
L3: unificata, interna fino a 3 MB accessibile in minimo 12, 13 o 14 (dato intero, virgola mobile o istruzione)
cicli di clock
Amd Athlon 64FX
L1: 64KB instruction cache + 64KB data cache
L2: 1024KB
PDF Creator - PDF4Free v2.0
http://www.pdf4free.com
In questa tabella ho riportato solo valori considerati attendibili, in maggioranza reperiti nei siti ufficiali dei
rispettivi produttori. Da notare che l’AMD 64FX utilizza una cache di primo livello molto ampia, in parte
perché necessità di maggior spazio per indirizzamento (e non solo) trattando parole a 64bit. Sono stati
volutamente scelti i modelli top della gamma delle rispettive case produttrici per una certa fascia di mercato.
Potete notare anche come il PIV abbia una cache di primo livello molto ridotta ( il valore in tabella
è riferito anche ad un qualunque processore Intel Pentium IV, anche non Extreme Edition) , anche
inferiore al suo antenato PII (16KB dati + 16KB istruzioni) dovuta al fatto che si è preferito
integrare la L3 sullo stesso chip del processore fino alla ragguardevole soglia dei 2 MB in modo da
creare una gerarchia più scalare. Inoltre poiché la cache produce dei problemi di “spazio” che
cominciano a diventare pressanti per i processori Intel, questa soluzione rappresenta la migliore
configurazione possibile con processori dotati di una simile architettura.
Occupiamoci ora delle modalità che consentono alla cache di essere estremamente prestante, molte
volte più prestante di una normale memoria centrale (di qualunque tipo). Innanzitutto non si può
dimenticare il modo in cui la cache è costruita: normalmente fino al livello L2 la cache è costituita
dalla cosiddetta RAM statica (SRAM), tramite dei circuiti molto simili ai flip-flop di tipo D (per la
precisione, una serie di questi flip-flop). Per i nostri scopi non ci interessa sapere in che modo questi
sono fatti, ci basta sapere che non sono degli elementi elementari. Al contrario la tradizionale RAM
che i personal computer montano si chiama RAM dinamica, e non utilizza la tecnologia a flip-flop.
Attualmente si utilizza uno schema misto, la cosiddetta SDRAM, che però non è paragonabile alla
RAM statica pura, quindi verrà qui di seguito omessa. Essenzialmente ogni cella di RAM dinamica
è composta da un condensatore e un transistor, nient’altro. Da questo banalmente segue che la RAM
dinamica è poco costosa da produrre, e si può quindi installare in gran quantità, mentre la RAM
statica è estremamente costosa, e sebbene sia possibile in linea teorica progettare un calcolatore che
usi solo RAM statica questa è un’operazione che avrebbe dei costi spropositati (pensate a quanto
costa il vostro processore da solo: esso al suo interno non contiene probabilmente più di 512KB di
L2, e al massimo 32KB di L1). Inoltre la carica della RAM dinamica ha ovviamente bisogno di
refresh ad intervalli regolari di tempo, e perciò necessità di un controller molto più sofisticato della
RAM statica, e quindi della cache. La particolare struttura della RAM statica fa anche in modo che
essa sia particolarmente voluminosa (i flip flop sono costruiti con vari transistor) il che ne rende
difficoltosa l’implementazione di quantità troppo elevate. A parte questo dovrebbe essere chiaro che
il primo motivo per cui la cache è estremamente veloce, a parte il fatto che si trova molto
vicina all’ALU, è da ricercarsi nella tipologia di costruzione.
Il secondo motivo per cui la cache è estremamente prestante è da ricercarsi nel modo in cui è
organizzata e nel modo in cui è gestita questa organizzazione. Cominciamo col sottolineare che
tutte le operazioni di gestione della cache sono eseguite direttamente in hardware. Questa
osservazione, che potrebbe sembrare banale, in realtà è di importanza vitale per avere una cache
prestante. Se ci fossero operazioni da eseguire via software (quindi passando via ALU) poiché
normalmente anche le operazioni di gestione elementari della memoria sarebbero scomposte in
numerose micro-iscruzioni non sarebbe in nessun modo avere tempi di reazione della memoria
calcolabili in pochi cicli di clock (in alcuni casi si arriva fino ad un solo ciclo di clock).
Per sfruttare al meglio il principio di località la cache è organizzata a linee, esattamente come la
memoria centrale. Ogni linea della cache è lunga più di un singolo dato o una singola parola, e
contiene normalmente un campo validatore (che possiamo per i nostri scopi ignorare), un campo
che contiene l’indirizzo in memoria centrale cui si riferisce la linea di cache e il campo dati. Per
come è strutturata la cache emerge una proprietà interessante: possiamo fare in modo che ogni linea
di cache sia potenzialmente usata solo da alcune locazioni di memoria centrale, per fare in modo
che la ricerca in cache sia estremamente veloce (se sappiamo già dove cercare, possiamo concludere
in fretta se abbiamo o no trovato quello di cui avevamo bisogno). Se facciamo in modo che la prima
di n linee di cache sia riferita alla prima parte della memoria centrale (in modo che ogni riga sia
PDF Creator - PDF4Free v2.0
http://www.pdf4free.com
riferita a parti successive) non rispettiamo il principio di località, anzi peggioriamo le prestazioni
della macchina poiché alla richiesta di un dato non presente andremmo in memoria centrale a
cercare una intera linea invece del solo dato che ci serve! Per questo motivo l’intera cache
rappresenta una parte consecutiva della memoria centrale. In altre parole, gli indirizzi da 0 a x
sono riferiti alla cache dalla linea 0 alla linea n, allo stesso modo gli indirizzi da x+1 a 2x. Come
funziona a questo punto il procedimento di ricerca in cache di una certa parola di memoria?
Banalmente con una operazione di modulo e resto (che non mettiamo per non appesantire
ulteriormente la discussione) si calcola quale è la linea di cache in cui, se presente, ci deve essere il
valore cercato. A questo punto si confronta il valore indirizzo di questa linea con quello cercato: se
coincidono abbiamo trovato il nostro dato e ci siamo risparmiati un lentissimo accesso in memoria
centrale, diversamente dobbiamo caricare dalla stessa l’intera linea di memoria e metterla in questa
stessa linea. Questa è la modalità di funzionamento di una cache “semplice” chiamata “a mappatura
diretta”. Rimandiamo al lettore particolarmente interessato lo studio di come si comporta una cache
più complessa, denominata set-associative.
Con queste mie considerazioni spero di aver introdotto in maniera piacevole un argomento
complesso come quello delle memorie e della cache in particolare. In particolare in questa sede si è
volutamente posto l’accento su come il problema alla base della struttura delle nostre macchine sia
tipicamente economico, e questo intrinsecamente influenza le prestazioni delle stesse. Si sono anche
enumerate brevemente le peculiarità che rendono la cache una memoria così prestante, seppur in
maniera volutamente schematica. Mi auguro quindi che il presente possa essere stato utile sia a chi
non aveva alcuna conoscenza di questi argomenti e non ha interesse ad approfondire ulteriormente
l’argomento sia a chi potrà tranne spunti per ulteriori ricerche.
PDF Creator - PDF4Free v2.0
http://www.pdf4free.com