Caricare documenti e articoli online 
INFtub.com è un sito progettato per cercare i documenti in vari tipi di file e il caricamento di articoli online.


 
Non ricordi la password?  ››  Iscriviti gratis
 

Memorie: considerazioni su funzionamento e prestazioni - funzionamento della memoria cache

informatica



Memorie: considerazioni su funzionamento e prestazioni

Prima parte: funzionamento della memoria cache

La memoria centrale e i "colli di bottiglia" - Cache memory: come e perché

Nuove soluzioni tecniche come cache sincrona o pipelined burst, EDO RAM, SDRAM, WRAM, integrazione on-chip della cache di secondo livello e così via riportano alla ribalta un argomento, quello dei timing delle memorie, che su BETA non ha ancora trovato una trattazione organica.

Converrà introdurre l'argomento ricordando il ruolo delle memorie in un moderno PC.

Il compito della CPU consiste nell'eseguire continuamente, alla massima velocità possibile, istruzioni che manipolano dati.
Sia le istruzioni, sia i dati su cui queste operano, sono contenuti nella memoria centrale del computer e vengono prelevati dalla CPU man mano che l'elaborazione va avanti. E' opportuno sottolineare che la CPU non è in grado di eseguire istruzioni o manipolare dati che non siano presenti nella memoria centrale: non può , ad esempio, prelevarli direttamente da memorie di massa o da ROM.
Una CPU moderna viaggia a frequenze molto elevate, tipicamente tra i 100 e i 200 Mhz (= milioni di cicli di clock al secondo); inoltre, è in grado di eseguire le istruzioni più comuni in pochi cicli di clock, spesso addirittura in un solo ciclo.
Risulta evidente, allora, come lo scambio di dati con la memoria centrale assumerà un ruolo fondamentale nelle prestazioni di un sistema: se non si ha una buona efficienza nei trasferimenti di dati ed istruzioni da e verso la CPU, anche la più veloce di esse non potrà che fornire prestazioni insoddisfacenti, dovendo continuamente attendere che i dati giungano a destinazione.
Le CPU per PC usate oggi hanno un'architettura interna a 32 bit e un bus dati di 64 bit, quindi di per sè potrebbero tranquillamente sostenere throughput di dati molto elevati (nonostante un accesso alla memoria richieda comunque un minimo di 2 cicli di clock); ma costruire memorie centrali in grado di "star dietro" ad un simile traffico costerebbe moltissimo, risultando per di più incompatibile con le concorrenti esigenze di compattezza e dissipazione del calore (ricordiamo che sul mercato sono disponibili da te 252b17c mpo schedine SIMM in grado di contenere 32 MB di memoria, il tutto su un'area di pochi centimetri quadrati).



L'esigenze di mediare tra costi e prestazioni ha portato, dalla seconda metà degli anni '80 (in coincidenza con la comparsa dei processori Intel 80386), ad adottare architetture di sistema con impiego di cache memory.

Il principio è quello di interporre, tra la CPU e la memoria centrale, una piccola quantità di memoria ad alta velocità , in grado di "reggere il passo" della CPU e quindi agire da tampone, o se preferite da serbatoio di transito, nei confronti della lenta memoria centrale.
Il concetto di cache non è stato certo inventato allora, dal momento che già alla fine degli anni '70 veniva sfruttato nei sistemi di memoria virtuale dei grossi elaboratori (v. il sistema di paginazione della memoria in MULTICS); tuttavia per la prima volta veniva applicato su larga scala al problema specifico del collo di bottiglia tra CPU e RAM, e con ottimi risultati, al punto che oggi una certa quantità di cache memory viene direttamente integrata in tutte le CPU.

Il principio su cui si basa il successo della cache è noto come principio di localizzazione: la CPU tende, in media, a prelevare istruzioni e dati da locazioni di memoria abbastanza vicine tra loro, a causa della struttura stessi dei programmi e dei metodi di allocazione della memoria usati dai compilatori: questo principio fa sì che, per un certo periodo di tempo, la zona di memoria interessata dal lavoro della CPU (working set) sia abbastanza limitata, al punto da poter essere "copiata" in una piccola quantità di memoria molto veloce, appunto la cache memory (sullo stesso principio si basano i sistemi di caching delle memorie di massa, che siano software come SMARTDRIVE o hardware come le memorie installate a bordo delle unità stesse e dei "controller intelligenti").
Per un certo periodo di tempo, dunque, la CPU potrà prelevare i dati di cui ha bisogno direttamente dalla cache memory, senza chiamare in causa la memoria centrale e quindi senza lunghe attese. Un principio analogo (anche se con maggiore approssimazione) vale per le scritture (movimento di dati dalla CPU alla memoria).

Esistono diversi metodi di caching, e quindi diverse architetture di cache memory: le distinzioni principali si fanno per metodo di mappatura della memoria (tramite il quale vengono individuate le categorie: cache a mappa diretta, cache associative e cache associative ad insiemi) e strategia di scrittura (abbiamo dunque le cache write-through e write-back). Le considerazioni che faremo valgono, comunque, in generale.

Per poter funzionare, una cache memory ha la necessità di essere riempita. Il procedimento adottato è il seguente: ogni volta che la CPU ha bisogno di un dato, se esso non è presente nella cache (cache miss) ci viene inserito, leggendolo dalla memoria centrale: la prossima volta che la CPU chiederà quel dato, potrà leggerlo direttamente dalla cache memory.
Per una serie di motivi architetturali (vedi i rimandi sui vari tipi di cache), non è però pratico inserire nella cache un dato (double word, ovvero 32 bit) alla volta: si sceglie allora di aggiornare un'intera linea.
Le dimensioni di una linea (o slot) dipendono da fattori quali le dimensioni della cache; comunque in generale si usa, per gli attuali PC 386/486, una linea da 16 byte (4 double word), che salgono a 32 (4 quad word, da 64 bit) per Pentium e Pentium Pro.

Ricapitolando, dunque, ogni volta che avviene un cache miss occorre leggere dalla memoria centrale un'intera linea (line fill).
In linea di massima, ciò suona poco efficiente: considerando che una lettura da memoria centrale può richiedere diversi cicli di clock, 4 letture per cache miss potrebbero costituire un pedaggio un po' troppo alto, nonostante l'elevata percentuale di successo (hit rate) assicurata generalmente dai sistemi di cache.

Per ridurre questo problema prestazionale, viene utilizzata una tecnica denominata burst read.
Considerando la struttura in cui è organizzata la memoria cache (mappatura), e il principio di localizzazione prima ricordato, appare chiaro come ogni cache line sia costituita da 4 "parole" consecutive, ovvero occupanti, nella memoria centrale, posizioni adiacenti.
Ciò significa che, una volta conosciuta la posizione di una parola della linea, quella delle altre 3 è facilmente ricavibile: non c'è alcun bisogno di richiedere esplicitamente tutti e 4 gli indirizzi!
Questo permette di risparmiare tempo: una volta richiesto il primo indirizzo, il chip che sovrintende al funzionamento della cache (la stessa Memory Unit della CPU se la cache è integrata, uno degli integrati che costituiscono il chipset della mother board se parliamo di cache esterna) preleva automaticamente dalla memoria le altre 3 parole e l'intera operazione impiega un minor numero di cicli di clock.

Più in dettaglio: la prima delle 4 parole viene letta in modo convenzionale, per cui richiederà un certo numero M di cicli di clock; le 3 letture successive non richiederanno la generazione ex-novo dell'indirizzo, per cui richiederanno ciascuna un numero N di cicli di clock, con N <= M-1 (il numero di cicli risparmiati dipende da un certo numero di fattori, anzitutto la velocità e l'organizzazione logica della memoria centrale).
Il timing di un burst read sarà quindi del tipo M-N-N-N, e corrisponderà ad un'operazione di line fill: ecco quindi spiegato il significato di opzioni del Chipset Setup del tipo:

RAM Burst Read:    7-4-4-4

Torneremo comunque sull'argomento dei timing in un successivo articolo sui vari tipi di memoria disponibili.

Il meccanismo sopra descritto era riferito ad accessi tra CPU e memoria centrale, con interposto un solo stadio di cache (cache di primo livello); tuttavia, l'estensione al caso di cache a più livelli è immediata.

Supponiamo infatti di avere 2 livelli di cache, ad esempio una cache integrata nella CPU (primo livello, "1L") ed una esterna, sulla mother board (secondo livello, "2L"); se avviene un cache miss sulla cache di primo livello, abbiamo due possibilità : il dato può essere presente nella cache di secondo livello (che generalmente è di dimensioni molto maggiori), ed allora ci sarà un line fill sulla cache 1L (operazione particolarmente veloce, dato che stiamo comunque leggendo da una cache memory, la 2L); oppure il dato può mancare anche dalla cache 2L (avremo 2 cache miss) e dovremo effettuare, leggendo dalla memoria centrale, un line fill per entrambe le cache.

Per la scrittura il meccanismo di base è praticamente lo stesso.
Quando la CPU scrive un dato, se questo è contenuto nella cache va modificato: per i motivi sopra esposti, l'intera cache line viene considerata "non più valida" (sporca), nel senso che non corrisponde più al contenuto della memoria centrale. Se la cache è di tipo write-through, il problema non sussiste in quanto il dato verrà scritto immediatamente anche nella memoria centrale, quindi la cache line non rimane mai "sporca"; tuttavia, nelle architetture write-back si dovrà , ad un certo punto, scrivere in memoria l'intera cache line.
Poiché anche le scritture sono operazioni piuttosto lunghe quando sono effettuate nella RAM, è stato previsto anche qui un meccanismo "burst": poiché le locazioni interessate alla scrittura sono consecutive, solo la prima parola necessiterà del processo completo, mentre le 3 successive verranno scritte in modo più rapido; quindi troviamo ancora un timing di tipo M-N-N-N. Va notato che i timing di burst read non corrispondono necessariamente con quelli di burst write, essendo in generale diversi i tempi di accesso in lettura da quelli in scrittura, specie in memoria centrale (le scritture sono, in genere, leggermente più veloci grazie alla presenza di buffer).

Naturalmente, anche per le scritture il discorso fatto per 1 livello di cache si puograve estendere facilmente al caso di più livelli, anche di tipo diverso (esempio: cache 1L write-through, cache 2L write-back).

A questo punto, è opportuno gettare uno sguardo alla storia della memoria cache su PC:

Con la comparsa del processore Intel 80386 (seconda metà anni '80), appaiono "timidamente" i primi sistemi di cache su alcune mother board: l'adozione della cache memory è consigliata dalla stessa Intel, che integra nella documentazione del nuovo processore alcuni schemi di caching, basati su un RAM-controller di propria produzione.
Le prime schede avevano 8 KB di cache memory da 25 nanosecondi, organizzata a mappa diretta e di tipo write-through. Verso la fine dell'era 386, però , erano diffuse schede con 128 KB di cache memory da 15 ns, in write-back.

Durante la progettazione dell'Intel 80486, divennero evidenti i limiti architetturali imposti dalla compatibilità x86, in particolare il ridotto numero di registri interni a disposizione che comporta la necessità di frequenti accessi alla memoria. Per motivi prestazionali si decise, quindi, di integrare nella CPU 8KB di cache memory: la prima volta nella storia dell'hardware x86.
La cache integrata era del tipo associativo a 4 insiemi ed era in grado di funzionare alla stessa velocità di un registro interno; proprio alla sua presenza era dovuta buona parte dell'incremento prestazionale a parità di frequenza rispetto al 386.

Poco dopo la comparsa dei 486 Intel, la Cyrix (alleata con IBM, Texas Instruments e SGS-Thompson) mise sul mercato delle CPU (486 DLC, SLC, DRX, DRX2, SLC2, Blue Lightning, ecc.) in grado di migliorare le prestazioni delle schede 386, semplicemente sostituendo la CPU 386 (compatibilità a livello di piedinatura); anche qui, la presenza di una cache memory integrata (pur se ridotta ad 1 KB, associativa a 2 insiemi) permetteva un apprezzabile incremento prestazionale, nonostante la logica di controllo integrata troppo primitiva entrasse in crisi con i trasferimenti DMA (per evitare problemi di coerenza, la cache interna andava "svuotata" ad ogni traferimento DMA), con ripercussioni piuttosto negative sulle prestazioni del sistema. Inoltre, molte delle schede madri 386 non avevano logica di supporto per la cache interna, che andava quindi attivata con un programmino "ad hoc".

La famiglia 80486 (considerando anche i vari "cloni") è stata piuttosto longeva: negli ultimi periodi si è assistito alla nascita di versioni con moltiplicatore interno di frequenza (da x2 dei primi modelli a x4 degli ultimi AMD 5x86, venduti come plug-in replacement per 80486 ma vicini al Pentium come architettura interna) e con cache integrata più "importante": fino a 16 KB in write-back sugli ultimi modelli AMD e Cyrix, che sfoderano prestazioni spesso in concorrenza con i Pentium di fascia bassa.

Sui Pentium, la cache interna è passata a 16 KB, divisa in 8 KB per le istruzioni (con funzionamento write-through) e 8 KB per i dati (in write-back); ma è con l'ultimo nato, il Pentium Pro, che si è avuta la vera svolta: questo processore dal particolare layout rettangolare è infatti, in realtà , un "chip doppio", integrando un processore (dotato di cache di primo livello simile a quella del Pentium) ed una cache memory write-through sincrona (vi rimando al prossimo articolo sui vari tipi di cache) da 256 KB o 512 KB.

L'integrazione di ben due livelli di cache sulla CPU è una tecnica adottata per la prima volta su larga scala dalle CPU RISC Alpha 21164, ed ha diverse valide giustificazioni.
Anzitutto, la grande quantità di cache memory permette di avere un eccellente hit rate anche su sistemi pesantemente multitasking/multiutente, dove il principio di localizzazione ha minore validità, evitando di dover accedere spesso al bus esterno. E' questa un'esigenza sempre più importante, dal momento che le moderne CPU sono caratterizzate da un elevato rapporto di moltiplicazione tra il clock esterno (quello della mother board) e quello interno (core clock), il che comporta necessariamente un notevole collo di bottiglia quando sia necessario accedere alle memorie esterne (che siano cache o memoria centrale), a causa della minore ampiezza di banda disponibile. Esempio: il Pentium 200 Mhz funziona con una frequenza esterna di 66.6 Mhz su bus dati da 64 bit, il che significa che, nel caso migliore (0 stati di attesa), si può contare su un throughput massimo teorico di 532 MB/s (con 1 MB = 10^6 byte). Internamente (tra CPU e cache integrata), però , il transfer rate teorico di punta è di ben 1600 MB/s considerando un path dati interno sempre da 64 bit (in realtà , il bus interno cache-CPU è da 256 bit, dovendo fornire dati ed istruzioni alle due unità pipelined che costituiscono il "cuore ad interi" del Pentium; quindi il valore è , sempre sul piano teorico, 4 volte superiore!).

Il secondo grande vantaggio di avere una buona quantità di cache integrata è evidente in sistemi multiprocessore.
Qui, l'esigenza di assicurare la giusta coerenza tra i dati in elaborazione sulle varie CPU comporta una notevole complessità nella logica di gestione della cache memory; se ciascuna CPU ha una buona quantità di cache integrata, con il relativo supporto all'archittettura multiprocessore, il sistema può fare a meno di una cache memory esterna condivisa tra le varie CPU, semplificando nettamente la scheda madre ed abbattendo i costi. Proprio questa considerazione ha spinto Intel a fornire fino a 512 KB di cache 2L all'interno dei Pentium Pro, dotati di una logica di supporto per l'SMP (Simmetrical Multi Processing) particolarmente completa.


Accessi alla memoria

Per effettuare un accesso alla memoria, la CPU deve effettuare una serie di operazioni: si tratta, essenzialmente, di richiedere l'uso del bus, specificare di che tipo di operazione si tratta, fornire l'indirizzo ed effettuare il trasferimento vero e proprio (quindi leggere o scrivere le 32 o 64 linee di dati del bus).
Queste operazioni richiedono l'asserzione di determinati segnali sul bus della CPU, la loro stabilizzazione elettrica, la loro lettura (campionamento), interpretazione, ecc.; tutto ciò ha un "costo" in termini di cicli di clock.

Una lettura singola (nonché la prima lettura delle 4 che compongono un ciclo burst) può essere completata, dalle CPU 486 e successive, in un minimo di 2 cicli di clock; lo stesso vale per una scrittura. Le letture e le scritture di un burst successive alla prima, invece, possono teoricamente essere completate ciascuna in un ciclo di clock: avremmo quindi un timing minimo per letture burst e scritture burst di 2-1-1-1 .

Cache associativa

La cache associativa ha, per ogni slot, un campo denominato TAG che contiene la posizione in memoria centrale dei dati contenuti nello slot stesso.
Questo TAG viene aggiornato ogni volta che i dati nello slot cambiano, e viene consultato ogni volta che ha luogo un accesso in memoria, confrontandolo con l'indirizzo del dato richiesto: se c'è corrispondenza, l'accesso avrà luogo alla cache anziché alla memoria.
Le dimensioni del campo TAG dipendono dal numero di possibili indirizzi che deve puntare; un caso tipico (Pentium) prevede che la quantità di memoria centrale da "coprire" sia 64 MB, e la dimensione della cache line di 32 byte. Abbiamo evidentemente 2^21 possibili zone, indirizzabili con 21 bit, che quindi è la dimensione minima del campo TAG.

La percentuale non trascurabile di spazio occupato dal TAG (quasi il 10% della capacità totale della cache memory) e soprattutto la complessità delle operazioni da effettuare per controllare se un dato è nella cache (complessità che ha conseguenze prestazionali) hanno pregiudicato notevolmente la diffusione degli schemi a cache associativa "pura", che praticamente non è mai utilizzata nei moderni PC. Un notevole successo hanno ottenuto, invece, le architetture a mappa diretta ed associativa ad insiemi.

Cache a mappa diretta

Se nella cache associativa ogni slot poteva contenere dati provenienti da qualsiasi zona della memoria, lasciando al TAG il compito di individuarne l'indirizzo, nell'organizzazione a mappa diretta ogni slot ha una sua zona di competenza.

Le cache line di un sistema a mappa diretta sono rigorosamente ordinate in base alla posizione, che costituisce, da un certo punto di vista, una parte del TAG: in base all'indirizzo di memoria del dato in esame si seleziona una ben precisa cache line che conterrà (o contiene) il dato.
Appare subito evidente che una semplice codifica posizionale non è sufficiente: per renderla univoca (condizione necessaria per la corretta individuazione del dato) servirebbe una cache memory grande quasi quanto l'intera memoria centrale (ci sarebbe un rapporto 1:4 dovuto al fatto che ogni cache line ospita 4 parole)
In effetti, ad una stessa cache line "competono" più indirizzi (il numero dipende dal rapporto tra quantità di memoria da "coprire" e numero di slot): a selezionare quale tra questi possibili indirizzi sia quello effettivamente in uso pensa un campo TAG; a differenza dell'architettura associativa, perograve , il TAG deve contenere solo una parte dell'informazione relativa all'indirizzo e quindi puograve avere dimensioni sensibilmente più ridotte.
Riprendendo l'esempio fatto con la cache associativa, supponiamo di avere 64 MB di memoria da coprire con una cache da 256 KB, avente slot da 32 byte -avremo quindi 8192 slot-.
Poiché (64*2^20 / 32) / 8192 = 256, avremo che ogni cache line dovrà occuparsi di 256 possibili diversi indirizzi: basta un campo TAG da 8 bit per determinare univocamente l'effettivo indirizzo in uso, rispetto ai 21 bit che servivano per la cache associativa.
Un risparmio di 13 byte per slot non è male (alla fine, con i dati del nostro esempio si risparmiano 13 KB di SRAM); tuttavia, il principale vantaggio della cache a mappa diretta è la velocità di accesso: infatti, per verificare se un dato è presente in cache è sufficiente comparare (in logica combinatoria, quindi senza stati di attesa) il suo indirizzo in memoria centrale con l'unione del numero d'ordine dello slot (bit più significativi) e del TAG (bit meno significativi), unione effettuabile con semplice logica cablata. Poiché tale operazione va effettuata per ogni slot, è presente un circuito speciale che consente di confrontare in parallelo gli indirizzi di tutti gli slot della cache (il che rende la cache a mappa diretta leggermente più complessa a livello hardware rispetto a quella associativa).
In scrittura, l'operazione è ancora più rapida: filtrando in logica cablata l'indirizzo in memoria centrale del dato in esame si ottengono subito il numero d'ordine dello slot interessato (bit più significativi) e il valore da immettere nel campo TAG (bit rimanenti).

La cache a mappa diretta non è tuttavia esente da difetti: il principali sono una scarsa "flessibilità " alle dimensioni della memoria centrale da coprire (derivante dalla necessità di suddividere in modo preciso l'indirizzo del dato in un numero fissato di bit dati dal numero d'ordine -e quindi dal numero di slot, che sono in quantità fissa- e dal TAG) e i conflitti di cache generati dal fatto che ogni slot deve occuparsi di più zone di memoria: se un dato è presente nella cache, i suoi "antagonisti" (con indirizzo che afferisce a quello stesso slot) non potranno sicuramente esserci. Ciograve potrebbe creare una situazione paradossale in cui, se il working set è costituito da soli dati ad indirizzi in multipli del numero di slot, un solo slot della cache si trova a "tamponare" l'intero set di dati !
Questa è , appunto, una situazione paradossale; tuttavia il principio del conflitto resta valido, ed in effetti l'hit rate di una cache a mappa diretta è sempre inferiore a quello di una cache associativa di pari dimensioni.

Per tentare di risolvere il problema, si è cercata una soluzione intermedia: l'organizzazione associativa ad (N) insiemi ( N-way set associative ).

Cache associativa ad insiemi

Immaginiamo di avere N cache associative affiancate, con le "colonne" di slot scelte dal valore di un TAG, ed avremo lo schema di una cache associativa ad N insiemi: una via di mezzo tra la velocità di accesso di una cache a mappa diretta e l'elevato hit rate di una associativa pura; guardando da un altro punto di vista, se riduciamo ad 1 il numero di insiemi di una cache di questo tipo, riotteniamo la cache associativa; se riduciamo ad 1 il numero delle righe, invece, abbiamo una cache a mappa diretta.

Chiaramente, se aumentiamo troppo il numero degli insiemi aumenta anche il numero di potenziali conflitti di cache, riducendo l'hit rate: per questo motivo N vale in genere, nei sistemi in esame, 2 o 4.





Privacy




Articolo informazione


Hits: 1906
Apprezzato: scheda appunto

Commentare questo articolo:

Non sei registrato
Devi essere registrato per commentare

ISCRIVITI



Copiare il codice

nella pagina web del tuo sito.


Copyright InfTub.com 2024