Miért van a processzoroknak többszintű gyorsítótáruk?

Nem lenne jobb egy nagy gyorsítótár a több kisebb helyett? Szólhat a logikus kérdés. A válasz a processzorok működésében rejlik.

Chip illusztráció
Chip illusztráció

Nem lenne jobb egy nagy gyorsítótár a több kisebb helyett? Szólhat a logikus kérdés. A válasz a processzorok működésében rejlik.

A rövid válasz úgy szól, hogy azért vannak különböző gyorsítótár szintek, mert mindegyik szint más célt szolgál és kicsit más a működésük is. Ahogy haladunk az L1 gyorsítótártól felfelé, a magasabb szint úgy lesz egyre lassabb, cserébe több adatot tárol, több feladata van és kevesebb energiával is beéri.

A hosszú válasz egy történettel kezdődik.

Mesedélután

Képzeld el, hogy valamikor az 1960-as években irodai dolgozó vagy. A számítógépet még nem találták fel, és a munkád során rengeteget kell iratreferenciákat keresned (ahol a mappa egy csomó papírt tartalmaz).

Van egy asztalod, legyen ez az L1 gyorsítótár. Az asztalodon pedig iratok, kupacokba rendezve: amivel éppen dolgozol, amik készen vannak és olyanok is, amik valószínűleg a későbbiekben kelleni fognak még.

Az irodában van egy iratgyűjtőszekrény, legyen ez az L2 gyorsítótár. Ez a szekrény azokat az iratokat tartalmazza, amikkel manapság foglalkoztál, de most épp nincs rájuk szükséged. Ha végeztél egy irattal az asztalodon, akkor beteszed a szekrénybe. Az iratgyűjtő elérése az asztalodhoz képest nem azonnali: ki kell nyitnod a megfelelő fiókot, végig kell nézned az iratok indexeit, hogy megtaláld amit keresel. Ugyanakkor egész gyors.

Előfordul, hogy másoknak arra az iratra van szükségük, ami a te iratgyűjtődben van. Van egy srác, aki a kis iratgyűjtő kocsijával folyamatosan körbe-körbe járja az irodákat, legyen a neve Buster. Buster jelképezi a gyűrű buszt (ring bus), ami a processzormagokat köti össze.

Ha egy dolgozónak olyan iratra van szüksége, ami nincs meg a saját iratgyűjtőjében, akkor ír egy iratkérő cetlit Busternek, ami tartalmazza, hogy mire van szüksége. Az egyszerűség kedvéért feltételezzük, hogy Buster pontosan tudja, melyik irat hol található. A következő alkalommal, mikor bejön az irodába, Buster megnézi, kell-e valakinek bármi a te iratgyűjtődből. Ha igen, akkor csendben kiveszi és ráteszi a kocsijára. Amikor a kör során az irat az igénylő irodájába kerül, beteszi az iratgyűjtőjébe a papírt, majd otthagy egy kézbesítési értesítést.

Olykor előfordul az is, hogy Bustertől azt az iratot kérték el, ami nincs benne az iratgyűjtődben, hanem azt asztalodon van. Ilyenkor nem veheti el azt minden szó nélkül, helyette meg kell kérdeznie, végeztél-e már vele. Ha még nem, akkor az igénylővel meg kell egyeznetek, hogy mitévők legyetek. Erre természetesen szervezni kell egy megbeszélést, és bizonyára egy halom céges protokoll is van az ilyen esetekre… :)

Az iratgyűjtőszekrények általában tele vannak. Ez azt jelenti, hogy Buster nem teheti be csak úgy az új iratot, először helyet kell csinálnia azzal, hogy kivesz egy másik, régen használt iratot. Ezeket a régen használt papírokat Buster az alaksori archívumba viszi, ami az L3 gyorsítótár.

Az alaksorban az iratok sűrűn, papírdobozokban vannak elhelyezve a különböző polcokon. Az egyszeri irodai dolgozó egyáltalán nem jön le ide, még csak erre sem sétál, és amúgy sincs fogalma az iratkezelési rendszer részleteiről. Erre van az irattáros.

Amikor Buster lejön, bedobja az összeszedett régi fájlokat archiválásra a „be” tálcán. Továbbá mellékel egy kikérő cetlit azoknak az iratoknak a címeivel, amik nincsenek az emeleti iratgyűjtőkben, de kellenének. Nem várja meg, míg az irattáros megtalálja a kért iratokat, mivel az eltart egy darabig. Az irattáros elolvassa a cetlit, megkeresi a kért dolgokat, majd ráteszi a „ki” tálcára, ha megvannak. Így, amikor Buster megint lent jár, mindig elveszi, ami a „ki” tálcán van, ráteszi a kocsijára és kézbesíti az igénylőnek, amint az irodájában jár.

Viszont probléma, hogy rengeteg ilyen irat van, és még a hatékony tárolás ellenére sincs elég hely ahhoz, hogy minden irat elférjen az alaksorban. Éppen ezért, a legtöbb irat az irodán kívül kerül tárolásra. Az irodaépület a város egy szép részén helyezkedik el, és a bérleti díj túl magas ahhoz, hogy tárolónak használjuk. Éppen ezért a képzeletbeli cégünk bérel egy raktárt 30 percre a várostól, ahol a régi iratait tárolja. Legyen ez most a DRAM.

Az alaksor bejáratánál ül Megan, a vezető irattáros. Megan követi, hogy mely iratok vannak az alaksorban és melyek a raktárban. Amikor Buster lead egy kérvényt a „be” tálcára, akkor ő nézi meg, hogy az adott irat megtalálható-e az alaksorban, így annak megkeresését az irattárosokra bízhatja, vagy az épp a raktárban van.

Ha a raktárban található, akkor a kérést hozzáadja a kintről beszerzendő kérések jó nagy kupacához. Naponta talán egyszer vagy kétszer kiküld egy iratszállító autót a raktárba, hogy hozza el a kért iratokat. Miközben raktározásra is küld néhányat, mert mint említettem, az alaksor tele van, ezért helyet kell csinálni a raktárból érkező iratoknak.

Bustert nem érdeklik a raktárral kapcsolatos dolgok, az Megan munkája. Ő mindösszesen annyit tud, hogy a benyújtott kéréseire sokszor elég gyorsan megkapja a kért dolgokat, de előfordul, hogy néhány órába is beletelik, míg meglesznek.

A lényeg

A fenti kis történet lényege az volt, hogy nagyjából legyen egy elképzelésünk a „varázslatos” gyorsítótár működéséről és arról, milyen fontos a logisztika. Nem csak az irodában, de egy chip tervezésekor is nagyon fontos a hatékony logisztika.

Tehát a kérdés úgy szólt, hogy nem lenne-e jobb egy nagy gyorsítótár a több kisebb helyett.

Tegyük fel, hogy van egy 4 magos processzorod, melynek van 128 KB-os L1 adat gyorsítótára, 1 MB-os L2 gyorsítótára és 8 MB-os L3 gyorsítótára. Miért ne lehetne ezeket összevonni kicsit több, mint 9 MB méretűre?

A történetünkre visszatérve azért, amiért mindenkinek van egy saját 1,5 méter széles asztala, és nem ültetjük le az iroda összes dolgozóját egy 150 méteres elé.

A lényege annak, hogy valami az asztalodon van az, hogy könnyen és gyorsan elérd. Ha túl nagy az asztal, és 50 métert kell sétálnod, hogy elérd a szükséges iratot, akkor nem teljesül a könnyű és gyors elérés. Persze fizikailag ott van asztalodon, de gyakorlatilag ez nem sokat segít.

Ugyanez a helyzet az L1 gyorsítótárral. Ha több adatot tartalmaz, akkor fizikailag is nagyobb lesz. Egyebek mellett így lassul a hozzáférése és több energiát is igényel. Az L1 gyorsítótárakat úgy méretezik, hogy elég nagyok legyenek ahhoz, hogy hasznosak legyenek, de elég kicsik a gyors eléréshez.

Ezen kívül az L1 gyorsítótárnak más jellegű hozzáférésekkel kell dolgoznia, mint a magasabb szintűeknek. Először is, több van belőle: van L1 adat gyorsítótár, L1 utasítás gyorsítótár és egy ideje már van egy másik, utasításokat tartalmazó utasítás gyorsítótár, az uOp gyorsítótár, amit egyféle L0 utasítás gyorsítótárnak is fel lehet fogni.

Az L1 adat gyorsítótárat olyan adatok írására és olvasására használják, melyek mérete általában 1 és 8 byte között van, vagy ennél egy kicsit több (pl. SIMD utasítások). A gyorsítótár hierarchiában magasabban lévő tárak nem foglalkoznak byte szintű adatokkal. Az irodai analógiánkban az asztalodon lévő iratokat te döntöd el, hogy egyben vagy részenként kezeled. Viszont amikor az kikerül az iratgyűjtőszekrénybe vagy tovább, akkor az irat egyben, de sokkal inkább más iratokkal együtt kerül mozgatásra. Ugyanez igaz a memória alrendszerekre. Amikor egy processzormag a memóriához fér hozzá, akkor nem az egyes byte-okkal dolgozik a rendszer, hanem egyszerre nagyobb mennyiségű adatot kér le.

Az L1 utasítás gyorsítótár elérése másképpen történik, mint az adat gyorsítótáraké. Továbbá az L1 adat gyorsítótártól eltérően az utasítás gyorsítótár csak olvasható, legalábbis ameddig a hozzátartozó processzormag nem határoz másképp. Ugyanis általában az utasítás gyorsítótár csak közvetetten van írva: amikor egy magasabb szinten lévő (L2 és felette) gyorsítótárba adat kerül, akkor az utasítás gyorsítótár újratölti onnan az adatait.

Éppen ezért egy nagy L1 gyorsítótár olyan tervezést feltételez, ami két, egymással ellentétes feltételnek felelne meg. Emiatt kompromisszumokat kellene kötni, tehát valójában egyik célra se lenne megfelelő. Egy ilyen összetett, utasítás és adat gyorsítótárnak mind a két adatforgalmat kezelnie kellene, ami hatalmas terhelést jelentene.

Programozóként nagyon egyszerű megfeledkezni arról, hogy mekkora gyorsítótár sávszélesség szükséges az utasítások betöltéséhez, pedig nagyon nagy. Példának okáért, egy néhány éves Intel Core i7 processzor esetén, ha nem a uOp gyorsítótárból dolgozik, akkor órajelciklusonként 16 byte-nyi adatnak megfelelő utasítást képes betölteni az L1 utasítás gyorsítótárból és képes ezt addig fenntartani, amíg az utasítások végrehajtása tudja tartani a lépést azok dekódolásával.

Egy kis kitérő: Amikor az utasítások dekódolásáról beszélünk, arról van szó, hogy a bejövő utasítást a processzor lényegében „átfordítja” olyan elektronikus jelekké, amit a végrehajtásához szükséges belső processzor komponens megért. Ez azért jó, mert elfedi a processzor belső működését (nem is érdekel feltétlen), téged nem érdekelnek a például az ALU (Aritmetikai-logikai egység, a processzor számológépe) nyavalyái. Csak az, hogy a matematikai műveleted helyesen és minél gyorsabban hajtódjon végre, mindezt úgy, hogy egy általános kóddal menjen minden adott architektúrát ismerő processzoron. Tehát egy x86_64 v1-es utasítás ugyanúgy viselkedjen egy AMD Athlon 64-en (első 64 bites asztali processzor), mint egy friss és ropogós Ryzen processzoron. Nyilvánvalóan a belső felépítésük köszönőviszonyban sincs egymással, mégis ugyanaz az utasítás ugyanazt az eredményt adja. Csodás!

Szóval, 3 GHz-es órajelet feltételezve órajelenként 16 byte-nyi utasítás betöltésénél 50 GB/s nagyságrendű sávszélességről beszélünk. Magonként. És ez még csak és kizárólag az utasítások betöltése volt. Persze csak akkor, ha az utasításbetöltő folyamatosan tud dolgozni és nincs valami miatt akadályoztatva. Innen fentebb lépve az L2 gyorsítótár ennek a terhelésnek lényegében csak a töredékét kapja, hiszen az L1 gyorsítótár jól működik.

De ha egy összevont L1 adat és utasítás gyorsítótárat terveznél, akkor mindenképpen előre kell látnod az alkalmankénti párhuzamos és hatalmas adatforgalmat az utasítás és adat gyorsítótár részéről. (Gondolj egy olyan esetre, amikor néhány KB-nyi adatot másolsz memcpy-vel, amikor az adat forrása és célja is az L1 adat gyorsítótárban van.)

De ez csak általánosítás. A processzormagok sok memória hozzáférést képesek ciklusonként kezelni, amíg azok az L1 gyorsítótárakban vannak. Intel Haswell architektúra (és újabbak) esetén az L1 adat és utasítás gyorsítótárnál összesen jóval több, mint 300 GB/s adatforgalommal kell számolni, magonként. Azzal a feltétellel, hogy ezekhez megfelelőek az utasítások. Ez a valóságban nem igazán valószínű, de alkalmanként egy rövid időre előfordulhat.

Végül pedig az adatok megosztása. Ha visszagondolsz az irodai analógiára, akkor azért van mindenkinek saját asztala, mert az az ő privát munkaterülete és nem kell senkit se megkérdeznie, hogy elveheti-e az iratot. Az L1 gyorsítótárak pont így működnek: minden magnak ez a magán munkaterülete. Ez nagyon fontos.

Ha az irodában négyen osztoznátok egy nagy asztalon, akkor nem vehetnél el úgy el egy iratot. Nem csak a te asztalod lenne, ezért előfordulhat, hogy neked és a három társadnak pont egyszerre lenne szüksége az adott iratra. Minden egyes alkalommal, amikor valamit el akarsz venni asztalról, körbe kéne kérdezned, hogy „elvehetem?”. De ha már valaki korábban kezdett vele dolgozni vagy korábban jelezte, hogy kell neki, akkor várnod kell. Persze kidolgozhattok különböző megoldásokat a konfliktus kezelésére, de mindig szükség lesz koordinációra a többiekkel. Ami időbe kerül.

Pontosan ugyanez érvényes, amikor több mag osztozik egy gyorsítótáron. Nem kezdhet el dolgozni anélkül, hogy szólt volna a többieknek mindenről, amit a megosztott gyorsítótárban lévő adatokkal csinál.

Ezért privátak az L1 gyorsítótárak. Az L1 a te „munkaasztalod”. Amíg ott ülsz, addig dolgozhatsz azon, amin előtted van. Az L2 gyorsítótár („iratgyűjtőszekrény”) kezeli a többiekkel kapcsolatos koordinációt. A dolgozó (a processzor mag) ideje nagy részét az asztala előtt ülve tölti. Buster csak jön, felveszi az új irat kéréseidet, beteszi a korábban kért iratokat az iratgyűjtődbe, mindezt anélkül, hogy téged vagy bárki mást megzavarna a munkájában.

Csak akkor kell a dolgozónak megszakítania a munkáját és Busterrel beszélnie, amikor egyszerre akarják az iratgyűjtőszekrényt használni, vagy valakinek olyan adatra van szüksége, ami a dolgozó asztalán van.

Röviden, az L1 gyorsítótárnak az a legfontosabb dolga, hogy kiszolgálja a hozzátartozó processzormagot. Mivel exkluzívan az adott maghoz tartozik, ezért nagyon kevés koordinációra van szükség. Az L2 gyorsítótár is exkluzív, de már több dolga van: intézni az adatokat szállító busz forgalmát és a kommunikációt ahelyett, hogy ilyesmivel megszakítaná a processzormag működését (aminek jobb dolga is van).

Az L3 gyorsítótár már megosztott a magok között, ezért a hozzáférését globálisan koordinálni kell. Az analógiánkhoz visszatérve a dolgozók csak és kizárólag Busteren (tehát a buszon) keresztül érhetik el az iratokat. A busz egyféle szűk keresztmetszet: remélhetőleg az L1 és L2 gyorsítótárak csökkentették annyival a memóriaeléréseket, hogy a busz áteresztő képessége nem korlátozza a teljesítményt.

Így van, csak nem teljesen

Többféle gyorsítótár topológia van. Amit most elmagyaráztam, az a legtöbb asztali (és laptop) processzorra igaz: magonkénti L1 adat és utasítás gyorsítótár, magonkénti egységesített L2 gyorsítótár és megosztott L3 gyorsítótár, ahol a magok gyűrű busszal vannak összekötve.

Nem minden rendszer ilyen. Néhány, főleg régebbi rendszernél nincsenek szétválasztva az adat és utasítás gyorsítótárak. Mások teljes Harvard architektúrát valósítanak meg, ahol az utasítás és az adat memória teljesen külön vannak választva. Olykor az L2 gyorsítótárakon több mag is osztozik (gondolj egy irodahelyiségre, amiben több íróasztal és egy iratgyűjtőszekrény van). Ilyenkor az L2 gyorsítótár lényegében a processzormagok közti busznak a része. Léteznek processzorok L3 gyorsítótár nélkül, és olyanok is akadnak, amikben L3 és L4 is található. Továbbá a leírás nem szól semmit se azokról a rendszerekről, ahol több processzor is jelen van.

Továbbá csak a gyűrű buszról (Busterről, aki körbejár) volt szó, mert ez jól illeszkedik az analógiába. A gyűrű buszok viszonylag általánosak. Néha, főleg amikor két vagy három blokkot kell összekötni, ez a busz egy teljes háló. Máskor több gyűrű buszt kell összekötni egy kereszttartóval (crossbar). Ez a példánkban lehet egy több emeletes irodaépület, ahol csak egy Buster van, aki liftek segítségével járja a szinteket.

Szoftverfejlesztőként természetesen feltételezed, hogy varázslatosan össze tudod kötni az A modult a B modullal, és az adat csak úgy átteleportál egyik helyről a másikra. A memória működése manapság hihetetlenül bonyolult lett, miközben a programozó felé mutatott absztrakció nem több, mint egy nagy és lapos byte tömb.

De a hardver nem így működik. A dolgokat nem lehet csak úgy varázslatosan, egy láthatatlan rétegen keresztül összekötni. Az A és a B modul nem absztrakt koncepciók, hanem fizikai eszközök, igazából nagyon pici gépek, amik valódi, fizikai helyet foglalnak a szilíciumon. A chipeknek „rendezési tervük” van, ami nem valami vad elképzelés vagy belső vicc: egy igazi 2D térkép arról, hogy mi hova megy. Ha A-t össze akarod kötni B-vel, akkor valódi, fizikai kábelt kell kihúzni közéjük. A kábelezésnek pedig hely kell és energia a működésükhöz (annál több, minél hosszabbak).

Ha egy tucat kábel megy A és B között, akkor az azt jelenti, hogy ezek a kábelek más részegységek összekötésétől veszik el a helyet. Persze a chipeknél több rétegben mennek a kábelek, de ez még mindig komoly probléma. Keress csak rá a „wire routing congestion” kifejezésre, ha érdekel. A chipen belüli adatmozgatás valódi, pokolian nehéz logisztikai probléma.

Bár az irodai egy egyszerűen emészthető példa, a „ki kivel beszél” és a „hogyan néz ki a rendszer felépítése, van-e ennek egyáltalán bármi értelme?” kérdések nagyon fontosak mind a hardver, mind a rendszertervezés egészének oldaláról nézve, hiszen nagyon nagy hatással vannak az egészre. A metaforák pedig a valóság alatt meghúzódó koncepció elmagyarázásához igen hasznosak.

A cikk nagyban támaszkodik Fabian “ryg” Giesen Why do CPUs have multiple cache levels? blogbejegyzésére. Köszönet érte!