- Koherencia vyrovnávacej pamäte zabezpečuje, že všetky kópie rovnakých údajov v rôznych vyrovnávacích pamätiach a v pamäti RAM zostanú konzistentné aj na viacjadrových systémoch.
- Hierarchia vyrovnávacej pamäte so zdieľanou poslednou úrovňou zjednodušuje kontrolu konzistencie a znižuje priame prístupy do hlavnej pamäte.
- Protokoly koherencie používajú stratégie zneplatnenia alebo aktualizácie kópií, podporované stavmi a riadiacimi bitmi na riadok vyrovnávacej pamäte.
- Kompilátor a operačný systém môžu doplniť konzistenciu hardvéru vkladaním inštrukcií a konfiguráciou pamäte pre kritické obdobia.

Keď sa pozriete na diagram akéhokoľvek moderného viacjadrového procesora, vždy sa objaví rovnaký vzorec: viacero jadier, každé s vlastnou blízkou vyrovnávacou pamäťou a zdieľaná vyrovnávacia pamäť poslednej úrovne, ktorá slúži ako spoločný bod pred dosiahnutím RAM. Toto usporiadanie nie je náhodné ani rozmar konštruktérov, ale priama reakcia na kritický problém v paralelných systémoch: koherenciu vyrovnávacej pamäte.
Bez robustného mechanizmu konzistencie by každé jadro mohlo pracovať s inou a zastaranou verziou rovnakých údajov v pamäti , čo sa v reálnom programe premieta do jemných chýb, nepredvídateľných zlyhaní a dokonca aj zlyhaní systému. Preto je pochopenie toho, ako sa táto konzistencia udržiava – na hardvérovej aj softvérovej úrovni – kľúčom k pochopeniu výkonu a stability moderných viacjadrových procesorov.
Čo je koherencia vyrovnávacej pamäte: metafora terminálu
Predstavte si niekoľko ľudí sediacich pred rôznymi terminálmi, ktorí všetci upravujú ten istý dokument uložený na centrálnom serveri . Každá obrazovka zobrazuje kópiu súboru a všetky zmeny, ktoré jedna osoba vykoná, sa majú okamžite prejaviť na obrazovkách všetkých ostatných.
Aby to fungovalo, je potrebný synchronizačný mechanizmus, ktorý šíri zmeny dokumentu do všetkých terminálov, aby všetci vždy videli rovnakú verziu. Pokiaľ tento systém funguje, všetko je v poriadku: ktokoľvek upraví text, vie, že všetci ostatní uvidia novú verziu takmer okamžite.
Teraz si predstavte, že synchronizačný systém zrazu zlyhá. Každý človek pokračuje v úpravách, presvedčený, že pracuje na zdieľanom dokumente, ale v skutočnosti má každý terminál svoju vlastnú odpojenú lokálnu kópiu . Od tej chvíle sa zmeny vykonané jednou osobou nedostanú k ostatným a dokument sa začne nekontrolovateľne rozptyľovať.
V oblasti výpočtovej techniky by sa presne toto stalo, keby CPU nemalo spoľahlivý protokol konzistencie: jedno jadro upravuje dáta v pamäti, ale ostatné jadrá pokračujú v čítaní staršej verzie zo svojich súkromných vyrovnávacích pamätí . To vytvára úrodnú pôdu pre vážne logické chyby, poškodené dáta a správanie pri odstraňovaní chýb.
Koherencia vyrovnávacej pamäte je teda súbor mechanizmov, ktoré zabezpečujú, že vo viacjadrovom systéme si všetky kópie rovnakých údajov distribuované v rôznych vyrovnávacích pamätiach a RAM zachovávajú konzistentný stav . Aj keď existuje viacero kópií, systém sa musí správať, „ako keby“ existovala iba jedna.

Hierarchia vyrovnávacej pamäte a pamäte vo viacjadrovom procesore
Vyrovnávacie pamäte CPU sú malé, veľmi rýchle pamäte, ktoré uchovávajú kópie často používaných blokov pamäte RAM . Keď procesor vykonáva kód, namiesto neustáleho prístupu k (relatívne pomalej) pamäti RAM sa pokúša čítať z vyrovnávacej pamäte a zapisovať do nej, čím drasticky znižuje latenciu.
Trik je samozrejme v tom, že vyrovnávacie pamäte neukladajú „oficiálnu verziu“ údajov, ale iba dočasnú repliku . Podľa metafory terminálu by RAM predstavovala dokument na serveri, zatiaľ čo vyrovnávacie pamäte by boli lokálne obrazovky, ktoré zobrazujú kópie určitých častí súboru.
Vo viacjadrovom CPU sa návrh stáva zložitejším, pretože každé jadro má zvyčajne vlastnú súkromnú vyrovnávaciu pamäť úrovne 1 (L1) a dokonca aj úrovne 2 (L2) . Nad ne sa pridáva napríklad zdieľaná vyrovnávacia pamäť úrovne 3, ktorá sa nachádza medzi jadrami a radičom pamäte, ktorý poskytuje prístup k pamäti RAM.
Táto zdieľaná vyrovnávacia pamäť je zavedená, pretože povolenie priameho a intenzívneho prístupu všetkých jadier k pamäti RAM by spôsobilo konflikty prístupu, súboje na pamäťovej zbernici a výrazný pokles výkonu . Vyrovnávacia pamäť poslednej úrovne funguje ako spoločný „buffer“, ktorý znižuje prístupy k pamäti RAM a centralizuje veľkú časť dátovej prevádzky.
Okrem toho, mnohé architektúry organizujú vyrovnávacie pamäte inkluzívne: riadky uložené na úrovniach blízko procesora sú prítomné aj na vyšších úrovniach hierarchie . To znamená, že riadok, ktorý sa objaví v úrovni L1, je tiež v úrovni L2 a následne v úrovni L3. To má veľmi užitočný dôsledok pre konzistenciu: jednoduchá správna aktualizácia vyrovnávacej pamäte najnižšej úrovne stačí na riadenie stavu ostatných úrovní bez nutnosti neustáleho prístupu k pamäti RAM.
Prečo je zdieľaná vyrovnávacia pamäť na poslednej úrovni kľúčová pre konzistenciu
Bez tejto globálnej vyrovnávacej pamäte poslednej úrovne by každé jadro muselo kontrolovať konzistenciu priamo v hlavnej pamäti . Vždy, keď by sa zmenil riadok pamäte v súkromnej vyrovnávacej pamäti, bolo by potrebné skontrolovať, či ostatné jadrá uchovávajú kópiu toho istého riadku, a ak áno, aktualizovať ho alebo zneplatniť všade.
V systéme s mnohými jadrami by toto zaťaženie kontrol viedlo k obrovskému počtu transakcií do pamäte RAM , čím by sa znehodnotila veľká časť výhod rýchlych vyrovnávacích pamätí. Umiestnením zdieľanej vyrovnávacej pamäte medzi jadrá a pamäť môže procesor sústrediť riadenie koherencie na jedno medziľahlé miesto.
V mnohých implementáciách obsahujú vyrovnávacie pamäte na vyšších úrovniach (ďalej od procesora) kópie riadkov prítomných na úrovniach bližšie k jadru . Pri tejto organizácii musí protokol koherencie zabezpečiť iba synchronizáciu poslednej úrovne s hlavnou pamäťou a synchronizáciu súkromných úrovní každého jadra s úrovňou bezprostredne nad ňou.
Toto si možno predstaviť ako akúsi ruskú hniezdnu bábiku: vyrovnávacia pamäť tretej úrovne obsahuje obsah druhej a prvej úrovne , druhá úroveň obsahuje svoj vlastný obsah a obsah prvej úrovne a prvá úroveň pozná iba svoje vlastné riadky. Ovládaním „veľkej bábiky“ (poslednej úrovne) teda systém dokáže efektívnejšie koordinovať zvyšok.
Výsledkom je, že udržiavanie konzistencie sa stáva ekonomickejším z hľadiska dizajnu a pamäťovej prevádzky . Namiesto toho, aby protokol nútil každé jadro neustále pracovať s RAM, pracuje so zdieľanou vyrovnávacou pamäťou a odtiaľ riadi, ktoré riadky by sa mali aktualizovať alebo zneplatniť v súkromných vyrovnávacích pamätiach.
Metódy aktualizácie: zneplatnenie a aktualizácia kópií
Kritický problém nastáva, keď dve alebo viac jadier chcú takmer súčasne pristupovať k rovnakému riadku údajov, ktorý je replikovaný vo viacerých vyrovnávacích pamätiach . V tejto súvislosti systémy konzistencie zvyčajne používajú dve základné stratégie pri spracovaní zápisov.
Prvá metóda je založená na zneplatnení. Keď jadro potrebuje zapisovať do konkrétneho riadku vyrovnávacej pamäte, protokol zneplatní všetky kópie toho istého riadku, ktoré môžu existovať v ostatných vyrovnávacích pamätiach . Iba jadro, ktoré bude zapisovať, udržiava riadok v stave povolenom na čítanie a zápis; ostatné jadrá, ak chcú tieto údaje znova použiť, budú musieť riadok znova načítať z vyššej úrovne (alebo z pamäte) s aktualizovanou verziou.
Druhá stratégia zahŕňa aktualizáciu. V tomto prípade, keď jadro upraví riadok, systém sa pokúsi automaticky rozšíriť nový obsah do existujúcich kópií v ostatných vyrovnávacích pamätiach . Týmto spôsobom všetky vyrovnávacie pamäte, ktoré uložili daný riadok, dostanú aktualizovanú verziu bez toho, aby bolo potrebné ho neskôr zneplatniť a znova načítať.
Každý prístup má svoje výhody a nevýhody. Invalidácia je zvyčajne efektívnejšia, keď sú zápisy časté, pretože sa tým zabráni saturácii pamäťového systému aktualizáciami, ktoré ostatné jadrá nemusia okamžite potrebovať. Naopak, aktualizácia môže byť výhodná, keď mnoho jadier často číta rovnaké dáta, ktoré sa relatívne zriedkavo upravujú , pretože znižuje latenciu tým, že po každej invalidácii nie je potrebné znova načítať riadok.
V oboch prípadoch obe metódy využívajú dodatočné stavy a riadiace bity v riadkoch vyrovnávacej pamäte. Každý riadok zvyčajne obsahuje informácie o tom, či sa jeho obsah zhoduje s obsahom v pamäti RAM a či je zdieľaný, upravený, exkluzívny, rezervovaný atď. v závislosti od konkrétneho protokolu (MESI, MOESI, MSI atď.). To umožňuje hardvéru rýchlo sa rozhodovať o tom, čo robiť, keď dôjde k operácii čítania alebo zápisu na už replikovanom riadku.
Kontrola konzistencie medzi vyrovnávacou pamäťou a pamäťou
Priame overenie konzistencie medzi všetkými úrovňami vyrovnávacej pamäte CPU alebo GPU a hlavnej pamäte by bolo obrovskou úlohou, a to ako z hľadiska zložitosti návrhu, tak aj z hľadiska nákladov na výkon. Preto moderné systémy organizujú toto overovanie hierarchicky.
Pamäte cache najbližšie k procesoru (L1, L2) zvyčajne nie sú priamo pripojené k pamäti RAM, ale k ďalšej úrovni cache. To znamená, že konzistencia sa neoveruje voči hlavnej pamäti na každej úrovni, ale skôr voči bezprostredne vyššej úrovni . To znižuje počet prístupov k pamäti RAM a zjednodušuje logiku potrebnú na nižších úrovniach.
Porovnanie obsahu vyrovnávacej pamäte (cache) a obsahu pamäte RAM sa nakoniec vykonáva medzi vyrovnávacou pamäťou poslednej úrovne a hlavnou pamäťou . Ak si táto posledná úroveň udržiava správny a konzistentný stav a každá nižšia úroveň si udržiava konzistentnosť s úrovňou nad ňou, celá hierarchia zostáva konzistentná bez toho, aby bolo potrebné opakovane kontrolovať každý riadok v pamäti RAM.
Keď jadro zapíše do riadku vyrovnávacej pamäte a zmení jeho dáta, stav tohto riadku je označený tak, že sa už presne nezhoduje s kópiou uloženou v pamäti . Odtiaľ protokol koordinuje aktualizáciu: označí zodpovedajúce kópie v iných vyrovnávacích pamätiach ako rezervované alebo neplatné a v prípade potreby zapíše nový obsah do príslušného riadku hlavnej pamäte.
Táto kaskádová organizácia umožňuje postupné šírenie zmien z jadra, ktoré aktualizuje dáta, do hlavnej pamäte a kontrolovaným spôsobom prechádza cez každú úroveň vyrovnávacej pamäte. Týmto spôsobom sa udržiavanie konzistencie nestáva pre procesor neprekonateľným úzkym hrdlom.
Hardvérová koherencia verzus softvérová koherencia
Doteraz sme diskutovali o mechanizmoch konzistencie, ktoré sú implementované hlavne v hardvéri: protokoly, stavové bity, zdieľané vyrovnávacie pamäte atď. Existuje však aj iný prístup, ktorý sa snaží presunúť časť tejto zložitosti na softvér , konkrétne na kompilátor a operačný systém.
Softvérové schémy konzistencie sa snažia znížiť potrebu dodatočnej logiky na čipe analýzou kódu a prijímaním rozhodnutí počas kompilácie . Myšlienka je taká, že ak kompilátor dokáže odvodiť, kedy a ako sa pristupuje k určitým zdieľaným údajom, v mnohých prípadoch by mohol zabrániť ukladaniu týchto údajov do vyrovnávacej pamäte alebo explicitne spravovať ich viditeľnosť.
Tento prístup má jasnú výhodu: časť pracovnej záťaže sa presúva z behu na riešenie počas kompilácie . Namiesto toho, aby hardvér detekoval a riešil všetky konflikty za chodu, kompilátor sa ich snaží predvídať a generovať kód, ktorý sa vyhýba nebezpečným situáciám.
Nevýhodou je, že statická analýza kódu je obmedzená, a preto kompilátory bývajú konzervatívne . To znamená, že aby sa predišlo narušeniu konzistencie, často robia rozhodnutia, ktoré znižujú účinnosť vyrovnávacích pamätí. Ak majú podozrenie, že niektoré údaje by mohli byť problematické, často zabránia ich ukladaniu do vyrovnávacej pamäte alebo vynútia synchronizáciu častejšie, ako je nevyhnutne potrebné.
Preto, hoci sú tieto softvérové schémy teoreticky atraktívne, najmä pre zjednodušenie návrhu hardvéru, v praxi nenahrádzajú podporu koherencie integrovanú v samotnom CPU , ale skôr ju v niektorých špecifických scenároch dopĺňajú.
Úloha kompilátora v konzistencii vyrovnávacej pamäte
Kľúčovým prvkom softvérových prístupov ku konzistencii je úloha kompilátora. Kompilátor dokáže vykonať hĺbkovú analýzu kódu a určiť, ktoré zdieľané dátové štruktúry môžu byť nebezpečné pre ukladanie do vyrovnávacej pamäte . Na základe toho tieto prvky označí špeciálnym spôsobom alebo prispôsobí generovanie kódu.
Najjednoduchší a zároveň najkonzervatívnejší prístup je zabrániť ukladaniu zdieľaných dátových premenných do vyrovnávacej pamäte . To znamená, že každý prístup k týmto premenným vynúti prístup k hlavnej pamäti alebo k oblasti, ktorú nie je možné uložiť do vyrovnávacej pamäte. To zaručuje konzistenciu, ale stráca to mnoho príležitostí na zvýšenie výkonu, pretože zdieľaná štruktúra sa v skutočnosti môže používať súkromne počas určitých období alebo len na čítanie počas iných období.
V skutočnosti problém konzistencie vzniká iba v intervaloch, keď aspoň jeden proces môže do premennej zapisovať a iný proces ju môže čítať . Mimo týchto kritických období sa s premennou môže zaobchádzať ako s premennou určenou výhradne pre jedno vlákno alebo dokonca ako s efektívnou konštantou na určitý čas, čo umožňuje jej bezproblémové ukladanie do vyrovnávacej pamäte.
Najpokročilejšie stratégie kompilácie sa snažia identifikovať tie „bezpečné“ obdobia, počas ktorých možno zdieľanú premennú považovať za nekonfliktnú . Na tento účel kompilátor analyzuje cesty vykonávania, potenciálne súbežné prístupy a synchronizačné vzory (zámky, kritické sekcie atď.). Na základe tejto analýzy rozdeľuje životnosť premennej do fáz: niektoré sú vhodné na ukladanie do vyrovnávacej pamäte a iné vyžadujúce špeciálne spracovanie.
Počas kritických období, keď sa zistí súbežný prístup so zápismi, kompilátor vloží do vygenerovaného kódu ďalšie inštrukcie na vynútenie konzistencie vyrovnávacej pamäte . Tieto inštrukcie môžu vynútiť vyprázdňovanie vyrovnávacej pamäte, opätovné načítanie pamäte, pamäťové bariéry alebo prístup k oblastiam označeným ako neuložiteľné do vyrovnávacej pamäte v závislosti od programovacieho modelu a základnej architektúry.
Vzťah medzi kompilátorom, operačným systémom a hardvérom
Fráza „kompilátor vkladá inštrukcie do vygenerovaného kódu, aby vynútil konzistenciu vyrovnávacej pamäte“ by mohla viesť k domnienke, že operačný systém číta tieto inštrukcie, akoby išlo o nápovedy na vysokej úrovni , a na základe toho sa rozhodne, ako program spustiť. V skutočnosti je mechanizmus trochu odlišný.
Keď kompilátor pridá tieto typy inštrukcií, do binárneho súboru zavádza špecifické operácie podporované architektúrou alebo prostredím runtime . Môže napríklad vložiť inštrukcie na vyprázdňovanie vyrovnávacej pamäte, pamäťové bariéry, špeciálne inštrukcie na označenie oblastí ako neuložiteľných do vyrovnávacej pamäte alebo volania služieb operačného systému, ktoré konfigurujú atribúty pamäte.
Operačný systém neinterpretuje tieto inštrukcie ako „komentáre“ alebo „nápovedy“ napísané kompilátorom; jednoducho vykoná strojový kód ako akýkoľvek iný . Niektoré z týchto inštrukcií sú však navrhnuté tak, aby interagovali s pamäťovým subsystémom a správou vyrovnávacej pamäte, čím menia spôsob, akým CPU pristupuje k určitým údajom.
Inými slovami, kompilátor vykoná predbežnú analýzu a vygeneruje kód, ktorého spustenie vyvolá požadované správanie vyrovnávacej pamäte . Operačný systém spolupracuje tak, že určuje atribúty pamäte (oblasti ukladateľné alebo neukladateľné do vyrovnávacej pamäte, pravidlá zápisu atď.) a poskytuje synchronizačné primitívy, ale „nečíta“ špeciálne inštrukcie v zmysle ich sémantickej interpretácie ako to robí kompilátor.
Môže sa tiež stať, že hardvér po zistení určitých inštrukcií aktivuje špecifické mechanizmy koherencie alebo synchronizácie . Napríklad inštrukcie typu fence alebo barrier zaručujú poradie prístupu k pamäti a vynucujú určité efekty viditeľnosti v hierarchii vyrovnávacej pamäte. V tomto prípade existuje trojstranná spolupráca: kompilátor rozhoduje, kam tieto inštrukcie umiestni, operačný systém konfiguruje prostredie vykonávania a hardvér implementuje skutočné správanie na úrovni vyrovnávacej pamäte a pamäťovej zbernice.
Všetky tieto prvky spolu zabezpečujú, že aj pri viacerých kópiách rovnakých údajov distribuovaných v rôznych vyrovnávacích pamätiach a hlavnej pamäti bežia paralelné programy s konzistentným pamäťovým modelom . Koherencia vyrovnávacej pamäte, ktorá nie je len jednoduchým interným detailom CPU, sa stáva ústrednou súčasťou spoľahlivého a efektívneho fungovania viacjadrových systémov.
Pochopenie toho, ako sa hierarchia vyrovnávacej pamäte, protokoly hardvérovej koherencie a techniky softvérovej podpory kombinujú, objasňuje, prečo majú moderné návrhy CPU takú podobnú štruktúru a prečo môže malá porucha v ktoromkoľvek z týchto mechanizmov spustiť chaotické správanie v súbežných aplikáciách , ktoré sú úplne závislé od toho, aby všetky jadrá videli rovnaké údaje v správnom čase.