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