Safe-ul... calculatorului
Memorii cacheLucian VințanAcest articol încearcă să prezinte într-o manieră sintetică unul dintre cele mai fecunde concepte ale tehnologiei computerelor, anume acela de memorie cache. Cache-urile au un rol esențial, anume acela de "acoperire a gap-urilor tehnologice" între diversele componente ale sistemului ierarhizat de memorii. Cac he: a safe place for hiding or storing things. (Webster's New World Dictionary of the American Language, Third College Edition - 1988)
Memoria cache este o memorie situată din punct de vedere logic între CPU (Central Processing Unit - unitate centrală) și memoria principală (uzual DRAM - Dynamic Random Access Memory), mai mică, mai rapidă și mai scumpă (per byte) decât aceasta și gestionată - în general prin hardware - astfel încât să existe o cât mai mare probabilitate statistică de găsire a datei accesate de către CPU, în cache. Așadar, cache-ul este adresat de către CPU în paralel cu memoria principală (MP): dacă data dorită a fi accesată se găsește în cache, accesul la MP se abortează, dacă nu, se accesează MP cu penalizările de timp impuse de latența mai mare a acesteia, relativ ridicată în comparație cu frecvența de tact a CPU. Oricum, data accesată din MP se va introduce și în cache. Memoriile cache sunt implementate în tehnologii de înaltă performanță, având deci un timp de acces foarte redus (cca. 2-7ns la ora actuală). În prezent presiunea asupra acestor memorii este foarte ridicată, rolul lor fiind acela de a apropia performanța microprocesoarelor (care crește cu cca. 50-60% pe an) cu aceea a memoriilor DRAM, a căror latență scade cu doar cca. 7% pe an. În general, pentru a accesa o locație DRAM, un procesor "pierde" 10-30 de impulsuri de tact (~ timp acces DRAM/TCLK, TCLK= perioada ceasului microprocesorului), în schimb accesarea cache-ului se face în doar 1-3 impulsuri de tact. Cu alte cuvinte, memoria cache reduce timpul mediu de acces al CPU la MP, ceea ce este foarte util. Se definește un acces al CPU cu hit în cache, un acces care găsește o copie în cache a datei accesate. Un acces cu miss în cache este unul care nu găsește o copie în cache a datei accesate de către CPU și care, prin urmare, adresează MP cu toate penalizările de timp care derivă din accesarea acesteia. Se definește ca parametru de performanță al unei memorii cache rata de hit, ca fiind raportul statistic între numărul acceselor cu hit în cache și numărul total al acceselor CPU la memorie. Măsurat pe benchmark-uri (programe de test) reprezentative, la ora actuală sunt frecvente rate de hit de peste 90%. Rata de miss (RM) este complementara ratei de hit (RH), astfel că: RH [%] + RM [%]=100 %. În esență, utilitatea cache-ului derivă din următorul fapt: la o citire cu miss (din MP), data adusă din MP este introdusă și în cache, în speranța că la o următoare citire a aceleiași date, aceasta se va găsi în cache (hit). În realitate, în cazul unei citiri cu miss în cache se aduce din MP nu doar data (cuvântul) dorită de către CPU ci un întreg bloc (4-16 cuvinte) care evident conține data respectivă. O citire cu miss presupune aducerea blocului din MP dar înainte de aceasta se impune evacuarea în MP a unui bloc din cache. Așadar, transferul din cache în MP se face tot la nivel de bloc și nu de cuvânt. Astfel, se optimizează traficul între cache și MP pe baza a două principii care vor fi discutate în continuare. În esență, eficiența memoriilor cache se bazează pe două principii de natură statistică și care caracterizează intrinsec noțiunea de program: principiile de localitate temporală și spațială. Conform principiului de localitate temporală, există o mare probabilitate ca o dată (instrucțiune) accesată acum de către CPU să fie accesată din nou, în viitorul imediat. Conform principiului de localitate spațială, există o mare probabilitate ca o dată situată în imediata vecinătate a unei date accesate curent de către CPU, să fie și ea accesată în viitorul apropiat (pe baza acestui principiu statistic se aduce din MP în cache un întreg bloc și nu doar strict cuvântul dorit de către CPU). O buclă de program (structură esențială în orice program) exemplifică clar aceste principii și justifică eficiența conceptului de cache. O combinare a celor două principii anterior expuse conduce la celebra "regulă 90/10" care spune că cca. 90% din timpul de rulare al unui program se execută doar cca. 10% din codul acestuia. Personal, cred că mai puțin. Pe baza acestor principii empirice se situează întreg eșafodajul conceptului de cache; eficiența sa deosebită nu poate fi explicată prin considerente analitice pentru simplul fapt că este practic imposibil a descrie analitic noțiunea de program. În fond, ce este un program? Care este distribuția instrucțiunilor sau a primitivelor structurale într-un program? Poate fi aceasta descrisă concret, pe baza unor modele deterministe sau aleatoare? Dificultatea unor răspunsuri exacte la aceste întrebări - dată în fond de imposibilitatea punerii în ecuație a minții umane, cea care creează infinita diversitate de "programe" - face ca cea mai bună explicație asupra eficienței memoriilor cache să stea în cele două principii empirice anterior schițate, caracterizând intrinsec noțiunea de program. Din punct de vedere arhitectural, există trei tipuri distincte de memorii cache în conformitate cu gradul de asociativitate: cu mapare directă, semiasociative și total asociative. La cache-urile cu mapare directă, ideea principală constă în faptul că un bloc din MP poate fi găsit în cache (hit) într-un bloc unic determinat. În acest caz regula de mapare a unui bloc din MP în cache este: (Adresa bloc MP) modulo (Nr. blocuri din cache) Strictețea regulii de mapare conduce la o simplitate constructivă a acestor memorii, dar și la fenomenul de interferență al blocurilor din MP în cache. Astfel, de exemplu, blocurile 12, 20, 28, 36, 42 etc. nu pot coexista în cache la un moment dat întrucât toate se mapează pe blocul 4 din cache. Prin urmare, o buclă de program care ar accesa alternativ blocurile 20 și 28 din MP ar genera o rată de hit egală cu zero. La cache-urile semiasociative există mai multe seturi, fiecare set având mai multe blocuri componente. Aici, regula de mapare precizează strict doar setul în care se poate afla blocul dorit, astfel: (Adresa bloc MP) modulo (Nr. seturi din cache) În principiu, blocul dorit se poate mapa oriunde în setul respectiv. Mai precis, la un miss în cache, înainte de încărcarea noului bloc din MP, trebuie evacuat un anumit bloc din setul respectiv. În principiu există implementate două tipuri de algoritmi de evacuare: pseudorandom (cvasialeator) și LRU (Least Recently Used). Algoritmul LRU evacuează blocul din cache cel mai demult neaccesat, în baza principiului de localitate temporală (aflat oarecum în contradicție cu o probabilistică markoviană care ar sugera să fie păstrat!). Dacă un set din cache-ul semiasociativ conține N blocuri atunci cache-ul se mai numește "tip N-way set associative". Este evident că într-un astfel de cache rata de interferență se reduce odată cu creșterea gradului de asociativitate N. Aici, de exemplu, blocurile 12, 20, 28 și 36 pot coexista în setul 0. Prin reducerea posibilelor interferențe ale blocurilor, creșterea gradului de asociativitate determină îmbunătățirea ratei de hit și deci a performanței globale. Pe de altă parte însă, asociativitatea impune căutarea după conținut (se caută deci într-un set dacă există memorat blocul respectiv) ceea ce conduce la complicații structurale și deci la creșterea timpului de acces la cache și implicit la diminuarea performanței globale. Optimizarea gradului de asociativitate, a capacității cache, a lungimii blocului din cache etc., nu se poate face decât prin laborioase simulări software, variind toți acești parametri în vederea minimizării ratei globale de procesare a instrucțiunilor (instrucțiuni/ciclu). În fine, memoriile cache total asociative, implementează practic un singur set permițând maparea blocului practic oriunde în cache. Ele nu se implementează deocamdată în siliciu datorită complexității deosebite și a timpului prohibit de căutare. Reduc însă (practic) total interferențele blocurilor la aceeași locație cache și constituie o metrică superioară utilă în evaluarea ratei de hit pentru celelalte tipuri de cache-uri (prin comparație). Următoarele 3 scheme prezintă implementări realizate pentru tipurile de cache anterior discutate. Cache 2-way set associative
Cache full associative
Cache direct mapat
S-a considerat un bloc compus din patru cuvinte. Bitul V este un bit de validare a blocului, V=1 fiind o condiție necesară a obținerii hitului. Bitul este util îndeosebi în sistemele multiprocesor în vederea menținerii coerenței memoriilor cache locale datorită redundanței informaționale. Mai precis, aici apare necesitatea citirii din cache-ul propriu a ultimei copii modificate a datei respective. Când un procesor modifică o copie locală a unei date, toate blocurile care conțin acea dată din cadrul celorlalte procesoare, trebuie invalidate prin resetarea V=0. Necesitatea invalidării blocurilor (V=0) apare chiar și în sistemele uniprocesor. Imediat după resetarea sistemului, uzual, procesorul execută un program încărcător rezident în memoria EPROM. Cum imediat după inițializarea sistemului conținutul cache-ului e practic aleator, pentru a evita false hituri la citirea programului încărcător din EPROM, se inițializează biții V cu zero. La prima încărcare a unei date (instrucțiuni) în cache, bitul V aferent se va seta pe 1', validând astfel hitul. Bitul D (Dirty) este pus pe 0' la încărcarea inițială a blocului în cache. La prima scriere a acelui bloc, bitul se pune deci pe 1'. Evacuarea propriu-zisă a blocului se face doar dacă bitul D are valoarea 1. Prin acest bit se minimizează evacuările de blocuri în MP, pe baza principiului că un bloc trebuie evacuat numai dacă a fost scris în cache. În acest sens, din punct de vedere al acceselor de scriere a unui procesor, există două posibilități: a) Strategia "Write Through" (WT), prin care informația este scrisă de către procesor atât în blocul aferent din cache cât și în blocul corespunzător din memoria principală. b) Strategia "Write - Back" (WB), prin care informația este scrisă numai în cache, blocul modificat fiind transferat în MP numai la evacuarea din cache. În vederea menținerii coerenței cache-urilor cu precădere în sistemele multimicroprocesor - există 2 posibilități în funcție de ce se întâmplă la o scriere: 1. Write Invalidate - prin care CPU care scrie determină ca toate copiile din celelalte memorii cache să fie invalidate înainte ca el să-și modifice blocul din cache-ul propriu. 2. Write Broadcast - CPU care scrie pune data de scris pe busul comun spre a fi actualizate toate copiile din celelalte cache-uri. Ambele strategii de menținere a coerenței pot fi asociate cu oricare dintre protocoalele de scriere (WT, WB) dar de cele mai multe ori se preferă WB cu invalidare. Nu detaliem aici problemele de coerență întrucât acestea se referă cu deosebire la problematica sistemelor multiprocesor și deci depășesc cadrul acestei prezentări. Apar posibile patru procese distincte într-un cache ca în tabelul următor:
Tip acces Hit/Miss Acțiune în cache Citire miss Evacuare bloc + încărcare bloc nou Citire hit (comparare tag-uri) Scriere miss Evacuare bloc + încărcare bloc nou + scriere dată în bloc Scriere hit Scriere dată în blocul din cache (WB)
Așadar, memoriile cache îmbunătățesc performanța îndeosebi pe citirile cu hit, iar în cazul utilizării scrierii tip Write Back și pe scrierile cu hit. Îmbunătățirea accesului la memorie pe citirile CPU este normală având în vedere că acestea sunt mult mai frecvente decât scrierile (orice instrucțiune implică cel puțin o citire din memorie pentru aducerea sa; statistic, cca. 75 % din accesele la memorie sunt citiri). Explicația la cauzele miss-urilor în cache-uri, conform literaturii acestui domeniu, sunt de trei tipuri: - datorită faptului că în fond primul acces la un bloc generează întotdeauna miss (compulsory); sunt inevitabile. - datorită capacității limitate a cache-ului care nu poate conține la un moment dat toate blocurile din MP, ceea ce implică evacuări/încărcări (capacity). - datorită interferențelor (conflictelor) unor blocuri din MP pe același bloc din cache (conflict); acestea se reduc odată cu creșterea capacității și a gradului de asociativitate. Primul care a pus în lumină conceptul de memorie cache a fost prof. Maurice Wilkes (Univ. Cambridge, Anglia) - un pionier al calculatoarelor care a inventat în 1951 și tehnica microprogramării unităților de comandă aferente procesoarelor - într-un articol publicat în 1965 ("Slave memories and dynamic storage allocation", IEEE Trans. Electronic Computers, April, 1965). Prima implementare a unui cache (cu mapare directă) aparține probabil lui Scarrott, în cadrul unui sistem experimental construit tot la Universitatea din Cambridge. Primul sistem comercial care utiliza cache-urile a fost IBM 360/85 (1968). Conceptul de cache s-a dovedit a fi foarte fecund nu numai în hardware dar și în software, prin aplicații diverse în sistemele de operare (memoria virtuală), rețele de calculatoare, baze de date, compilatoare etc.
Conf.dr.ing. Lucian Vințan este cadru didactic la Facultatea de Inginerie din Sibiu. Poate fi contactat prin e-mail la adresa vintan@cs.sibiu.ro [cuprins] |