Vad är cachestorlek? Vad påverkar processorcachen L1 L2 L3?

Alla användare är väl medvetna om sådana datorelement som processorn, som är ansvarig för att bearbeta data, såväl som random access memory (RAM eller RAM), som är ansvarig för att lagra dem. Men det är nog inte alla som vet att det också finns ett processorcacheminne (Cache CPU), det vill säga själva processorns RAM (det så kallade ultra-RAM).

Vad är anledningen till att datordesigner fick använda dedikerat minne för processorn? Räcker inte datorns RAM-kapacitet?

Under lång tid klarade sig persondatorer faktiskt utan något cacheminne. Men som ni vet är processorn den snabbaste enheten på en persondator och dess hastighet har ökat med varje ny generation av CPU. För närvarande mäts dess hastighet i miljarder operationer per sekund. Samtidigt har standard-RAM inte nämnvärt ökat dess prestanda under dess utveckling.

Generellt sett finns det två huvudsakliga minneschipteknologier - statiskt minne och dynamiskt minne. Utan att fördjupa sig i detaljerna i deras design, kommer vi bara att säga att statiskt minne, till skillnad från dynamiskt minne, inte kräver regenerering; Dessutom använder statiskt minne 4-8 transistorer för en bit information, medan dynamiskt minne använder 1-2 transistorer. Följaktligen är dynamiskt minne mycket billigare än statiskt minne, men samtidigt mycket långsammare. För närvarande tillverkas RAM-chips på basis av dynamiskt minne.

Ungefärlig utveckling av förhållandet mellan hastigheten på processorer och RAM:

Således, om processorn tog information från RAM hela tiden, skulle den behöva vänta på långsamt dynamiskt minne, och den skulle vara inaktiv hela tiden. I samma fall, om statiskt minne användes som RAM, skulle kostnaden för datorn öka flera gånger.

Det var därför en rimlig kompromiss togs fram. Huvuddelen av RAM-minnet förblev dynamiskt, medan processorn fick sitt eget snabba cache-minne baserat på statiska minneschips. Dess volym är relativt liten - till exempel är storleken på den andra nivåns cache bara några få megabyte. Det är dock värt att komma ihåg att hela RAM-minnet för de första IBM PC-datorerna var mindre än 1 MB.

Dessutom påverkas lämpligheten av att införa cachningsteknik också av det faktum att olika applikationer som finns i RAM laddar processorn på olika sätt, och som ett resultat finns det mycket data som kräver prioriterad bearbetning jämfört med andra.

Cachehistorik

Strängt taget, innan cacheminnet flyttade till persondatorer, hade det redan använts framgångsrikt i superdatorer i flera decennier.

För första gången dök ett cacheminne på endast 16 KB upp i en PC baserad på i80386-processorn. Idag använder moderna processorer olika nivåer av cache, från den första (den snabbaste cachen av den minsta storleken - vanligtvis 128 KB) till den tredje (den långsammaste cachen av den största storleken - upp till tiotals MB).

Till en början var processorns externa cache placerad på ett separat chip. Med tiden gjorde detta dock att bussen mellan cachen och processorn blev en flaskhals, vilket saktade ner datautbytet. I moderna mikroprocessorer finns både den första och andra nivån av cacheminne i själva processorkärnan.

Under lång tid hade processorer bara två cachenivåer, men Intel Itanium-processorn var den första som hade en tredje nivås cache, gemensam för alla processorkärnor. Det finns också utvecklingar av processorer med fyra-nivås cache.

Cachearkitekturer och principer

Idag är två huvudtyper av cacheminnesorganisation kända, som härstammar från den första teoretiska utvecklingen inom cybernetikområdet - Princeton- och Harvard-arkitekturer. Princeton-arkitekturen innebär ett enda minnesutrymme för att lagra data och kommandon, medan Harvard-arkitekturen innebär separata. De flesta x86 persondatorprocessorer använder en separat typ av cacheminne. Dessutom har en tredje typ av cacheminne också dykt upp i moderna processorer - den så kallade associativa översättningsbufferten, utformad för att påskynda konverteringen av operativsystemets virtuella minnesadresser till fysiska minnesadresser.

Ett förenklat diagram över interaktionen mellan cacheminne och processor kan beskrivas enligt följande. Först kontrollerar processorn förekomsten av den information som behövs av processorn i den snabbaste cachen på första nivån, sedan i den andra nivåns cache, etc. Om den nödvändiga informationen inte hittas på någon cachenivå, så kallar de det för ett fel eller en cachemiss. Om det inte finns någon information i cachen alls måste processorn ta den från RAM eller till och med från externt minne (från hårddisken).

Den ordning i vilken processorn söker efter information i minnet:

Så här söker processorn information

För att styra driften av cacheminnet och dess interaktion med processorns beräkningsenheter, såväl som RAM, finns det en speciell styrenhet.

Schema för att organisera interaktionen mellan processorkärnan, cache och RAM:

Cacheminnet är nyckellänken mellan processorn, RAM-minnet och cacheminnet

Det bör noteras att datacachning är en komplex process som använder många tekniker och matematiska algoritmer. Bland de grundläggande begreppen som används i cachelagring är cacheskrivningsmetoder och cacheassociativitetsarkitektur.

Cache-skrivmetoder

Det finns två huvudsakliga metoder för att skriva information till cacheminne:

  1. Återskrivningsmetod – data skrivs först till cachen och sedan, när vissa förhållanden uppstår, till RAM.
  2. Genomskrivningsmetod – data skrivs samtidigt till RAM och cache.

Cache-associativitetsarkitektur

Cache-associativitetsarkitektur definierar det sätt på vilket data från RAM mappas till cachen. De viktigaste alternativen för cachelagring av associativitetsarkitektur är:

  1. Direktmappad cache - en specifik sektion av cachen är ansvarig för en specifik sektion av RAM
  2. Helt associativ cache - vilken del av cachen som helst kan associeras med vilken del av RAM som helst
  3. Blandad cache (uppsättningsassociativ)

Olika cachenivåer kan vanligtvis använda olika. Direktmappad RAM-cache är det snabbaste cachealternativet, så den här arkitekturen används vanligtvis för stora cachar. I sin tur har en helt associativ cache färre cachefel (missar).

Slutsats

I den här artikeln introducerades du begreppet cacheminne, cacheminnesarkitektur och cachingmetoder, och lärde dig hur det påverkar prestandan hos en modern dator. Närvaron av cacheminne kan avsevärt optimera driften av processorn, minska dess vilotid och följaktligen öka prestanda för hela systemet.

Alla processorer sedan slutet av 90-talet har internt cacheminne (eller helt enkelt cache). Cachen är ett höghastighetsminne till vilket instruktioner och data som bearbetas direkt av processorn överförs.

Moderna processorer har inbyggt cacheminne på två nivåer - den första (L1) och den andra (L2). Processorn är något snabbare med innehållet i L1-cachen, medan L2-cachen vanligtvis är något större. Cacheminnet nås utan ett vänteläge, dvs. Nivå 1-cache (on-chip-cache) arbetar med processorhastighet.

Det betyder att om data som processorn behöver finns i cachen så finns det inga bearbetningsförseningar. Annars måste processorn hämta data från huvudminnet, vilket avsevärt minskar systemets prestanda.

För att kvalitativt förstå principen för driften av cacheminne på båda nivåerna, låt oss betrakta en vardaglig situation som ett exempel.

Du kommer till ett café för att äta lunch varje dag, vid samma tid, och alltid sitta vid samma bord. Beställ alltid ett standard tre-rätters set.

Servitören springer till köket, kocken lägger dem på en bricka och sedan kommer de med din beställning. Och så, säg, på tredje dagen, möter servitören dig, för att inte behöva springa till köket ännu en gång, på utsatt tid med en färdig varm lunch på en bricka.

Du behöver inte vänta på din beställning och spara mycket tid. Brickan med dina rätter är cachen på första nivån. Men den fjärde dagen vill du plötsligt lägga till en annan rätt, säg efterrätt.

Även om en bricka med din beställning redan väntade på dig vid utsatt tid, var servitören fortfarande tvungen att springa till köket för efterrätt.

Och på den femte - återigen en meny med tre poster. På den sjätte - efterrätt igen, men annorlunda än den föregående. Och servitören, utan att veta vilken efterrätt du vill beställa (och inte ens veta om du kommer att beställa något), bestämmer sig för att ta nästa steg: bredvid ditt bord sätter han ett skåp med flera typer av efterrätt.

Och om du uttrycker en önskan finns allt till hands, du behöver inte springa till köket. Dessertskåpet är en cache på andra nivån.

Processorns prestanda beror avsevärt på storleken på L1-cachen (från 16 till 128 KB) och L2 (från 64 KB till 512 KB, i Pentium III Heop och AMD Opteron upp till 4 MB).

Intel Pentium III-processorer och Celeron-processorer baserade på den har en 32 KB L1-cachestorlek. Intel Pentium 4, liksom Celeron- och Cheop-versionerna baserade på den, har bara 20 KB. AMD Duron, Athlon (inklusive XP/MP) och Opteron-processorer, samt VIA SZ, innehåller 128 KB L1-cache.

Moderna processorer med dubbla kärnor har en första nivås cache för varje kärna separat, så ibland i beskrivningen av cachen kan vi se siffran 128x2. Det betyder att varje processorkärna har 128 KB L1-cache.

Storleken på L1-cachen är viktig för att få hög prestanda i de flesta vanliga uppgifter (kontorsapplikationer, spel, de flesta serverapplikationer, etc.). Dess effektivitet är särskilt stark för threaded computing (till exempel videobehandling).

Detta är en av anledningarna till att Pentium 4 är relativt ineffektiv för de flesta vanliga applikationer (även om detta kompenseras av dess höga klockhastighet). L1-cachen fungerar alltid (byter information med processorkärnan) vid den interna processorfrekvensen.

Däremot fungerar L2-cachen i olika processormodeller vid olika frekvenser (och följaktligen prestanda). Från och med Intel Pentium II använde många processorer en L2-cache som arbetade med halva processorns interna frekvens.

Denna lösning användes i föråldrade Intel Pentium III-processorer (upp till 550 MHz) och föråldrade AMD Athlon (i vissa av dem fungerade den interna L2-cachen på en tredjedel av processorns kärnfrekvens). L2-cachestorleken varierar också mellan processorer.

I äldre och vissa nyare Intel Pentium III-processorer är L2-cachestorleken 512 KB, i andra Pentium III-processorer är den 256 KB. Den Pentium III-baserade Intel Celeron-processorn var tillgänglig med 128 och 256 KB L2-cache, medan den Pentium 4-baserade processorn var tillgänglig med endast 128 KB. Olika versioner av Xeon-versionen av Intel Pentium 4 har upp till 4 MB L2-cache.

De nya Pentium 4-processorerna (vissa serier med en frekvens på 2000 MHz och alla för högre frekvenser) har 512 KB L2-cache, resten av Pentium 4 har 256 KB. Xeop-processorer (baserade på Pentium 4) har 256 eller 512 KB L2-cache.

Dessutom har de även en L3-cache på tredje nivå. Den integrerade L3-cachen, i kombination med en snabb systembuss, bildar en höghastighetsdatautbyteskanal med systemminne.

Som regel är endast processorer för serverlösningar eller specialmodeller av "desktop"-processorer utrustade med L3-cacheminne. Till exempel har processorlinjer som Xeon DP, Itanium 2 och Xeon MP L3-cacheminne.

AMD Duron-processorn har 128 KB L1-cache och 64 KB L2-cache. Athlon-processorer (förutom de äldsta), Athlon MP och de flesta Athlon XP-varianter har 128 KB L1-cache och 256 KB L2-cache, och den senaste Athlon XP (2500+, 2800+, 3000+ och högre) har 512 KB L2 cache. AMD Opteron innehåller 1 MB L2-cache.

De senaste modellerna av Intel Pentium D, Intel Pentium M, Intel Core 2 Duo-processorer är tillgängliga med 6 MB L2-cache och Core 2 Quad - 12 MB L2-cache.

Den senaste Intel Core i7-processorn vid skrivandet av denna bok har 64 KB L1-cacheminne för var och en av de 4 kärnorna, samt 256 KB L2-minne för varje kärna. Förutom den första och andra nivåns cache, har processorn även en tredje nivås cache gemensam för alla kärnor, lika med 8 MB.

För processorer som kan ha olika L2-cachestorlekar (eller i fallet med Intel Xeon MP - L3) för samma modell måste denna storlek anges vid försäljningstillfället (naturligtvis beror priset på processorn på det). Om processorn säljs i ett "förpackat" paket (leverans i lådan) anges vanligtvis storleken på cacheminnet på den.

För normala användaruppgifter (inklusive spel) är hastigheten på L2-cachen viktigare än dess storlek; för serveruppgifter, tvärtom, är volym viktigare. De mest produktiva servrarna, särskilt de med stora mängder RAM (flera gigabyte), kräver maximal storlek och maximal hastighet för L2-cachen.

Cheop-versionerna av Pentium III-processorer förblir oöverträffade i dessa parametrar. (Xeon MP-processorn visar sig fortfarande vara mer produktiv i serveruppgifter än Pentium III Xeon, på grund av den högre klockfrekvensen hos själva processorn och minnesbussen.) Av ovanstående drar vi slutsatsen: cacheminne förbättrar interaktionen mellan en snabb processor och ett långsammare RAM, och låter dig även minimera väntetiderna som uppstår under databehandling. L2-cachen som finns på processorchippet spelar en avgörande roll i detta.

En av de viktiga faktorerna som ökar processorprestandan är närvaron av cacheminne, eller snarare dess volym, åtkomsthastighet och distribution mellan nivåer.

Sedan ganska länge har nästan alla processorer utrustats med denna typ av minne, vilket återigen bevisar användbarheten av dess närvaro. I den här artikeln kommer vi att prata om strukturen, nivåerna och det praktiska syftet med cacheminne, vilket är mycket viktigt. processoregenskaper.

Vad är cacheminne och dess struktur

Cacheminne är ultrasnabbt minne som används av processorn för att tillfälligt lagra data som är mest åtkomlig. Så här kan vi kort beskriva denna typ av minne.

Cacheminne är byggt på vippor, som i sin tur består av transistorer. En grupp transistorer tar upp mycket mer utrymme än samma kondensatorer som utgör Bagge. Detta medför många svårigheter i produktionen, såväl som begränsningar i volym. Det är därför cacheminne är ett mycket dyrt minne, samtidigt som det har försumbara volymer. Men från denna struktur kommer den största fördelen med sådant minne - hastighet. Eftersom vipporna inte behöver regenereras, och fördröjningstiden för grinden på vilken de är monterade är liten, sker tiden för att växla vippan från ett tillstånd till ett annat mycket snabbt. Detta gör att cacheminnet kan arbeta med samma frekvenser som moderna processorer.

En viktig faktor är också placeringen av cacheminnet. Den sitter på själva processorchippet, vilket avsevärt minskar åtkomsttiden. Tidigare låg cacheminne på vissa nivåer utanför processorkretsen, på ett speciellt SRAM-chip någonstans på moderkortet. Nu har nästan alla processorer cacheminne placerat på processorkretsen.


Vad används processorcache till?

Som nämnts ovan är huvudsyftet med cacheminne att lagra data som ofta används av processorn. Cachen är en buffert i vilken data laddas, och trots dess ringa storlek (ca 4-16 MB) moderna processorer, ger det en betydande prestandaökning i alla applikationer.

För att bättre förstå behovet av cacheminne, låt oss föreställa oss att organisera en dators minne som ett kontor. RAM-minnet kommer att vara ett skåp med mappar som revisorn periodvis kommer åt för att hämta stora datablock (det vill säga mappar). Och tabellen kommer att vara ett cacheminne.

Det finns element som placeras på revisorns skrivbord, som han refererar till flera gånger under en timme. Det kan till exempel vara telefonnummer, några exempel på dokument. Dessa typer av information ligger precis på bordet, vilket i sin tur ökar hastigheten för åtkomst till dem.

På samma sätt kan data läggas till från de stora datablocken (mapparna) till tabellen för snabb användning, till exempel ett dokument. När detta dokument inte längre behövs placeras det tillbaka i skåpet (i RAM-minnet), vilket rensar tabellen (cacheminne) och frigör denna tabell för nya dokument som kommer att användas under nästa tidsperiod.

Också med cacheminne, om det finns någon data som med största sannolikhet kommer att nås igen, så laddas denna data från RAM in i cacheminnet. Mycket ofta sker detta genom att samladda de data som med största sannolikhet kommer att användas efter den aktuella datan. Det vill säga att det finns antaganden om vad som kommer att användas "efter". Dessa är de komplexa driftsprinciperna.

Processorns cachenivåer

Moderna processorer är utrustade med en cache, som ofta består av 2 eller 3 nivåer. Visst finns det undantag, men så är det ofta.

I allmänhet kan det finnas följande nivåer: L1 (första nivån), L2 (andra nivån), L3 (tredje nivån). Nu lite mer detaljer om var och en av dem:

Första nivåns cache (L1)– den snabbaste cacheminnesnivån som arbetar direkt med processorkärnan, tack vare denna snäva interaktion har denna nivå den kortaste åtkomsttiden och fungerar på frekvenser nära processorn. Det är en buffert mellan processorn och den andra nivåns cache.

Vi kommer att överväga volymer på en högpresterande processor Intel Core i7-3770K. Denna processor är utrustad med 4x32 KB L1-cache 4 x 32 KB = 128 KB. (32 KB per kärna)

Andra nivåns cache (L2)– den andra nivån är större än den första, men har som ett resultat lägre "hastighetsegenskaper". Följaktligen fungerar den som en buffert mellan L1- och L3-nivåerna. Om vi ​​tittar igen på vårt exempel Core i7-3770 K, så är L2-cacheminnets storlek 4x256 KB = 1 MB.

Nivå 3 cache (L3)– den tredje nivån, återigen, är långsammare än de två föregående. Men det är fortfarande mycket snabbare än RAM. L3-cachestorleken i i7-3770K är 8 MB. Om de två föregående nivåerna delas av varje kärna, är denna nivå gemensam för hela processorn. Indikatorn är ganska solid, men inte orimlig. Eftersom till exempel för Extreme-seriens processorer som i7-3960X är det 15 MB och för vissa nya Xeon-processorer mer än 20.

Datorprocessorer har tagit betydande steg i utvecklingen under de senaste åren. Transistorernas storlek minskar för varje år, och produktiviteten ökar. Samtidigt är Moores lag inte längre aktuell. När det gäller processorprestanda bör du inte bara ta hänsyn till antalet transistorer och frekvens, utan också cachestorleken.

Du kanske redan har hört talas om cacheminne när du söker efter information om processorer. Men vanligtvis ägnar vi inte mycket uppmärksamhet åt dessa siffror; de sticker inte ens ut mycket i processorreklam. Låt oss ta reda på vad processorcachen påverkar, vilka typer av cache det finns och hur det hela fungerar.

Enkelt uttryckt är processorcachen helt enkelt väldigt snabbt minne. Som du redan vet har en dator flera typer av minne. Detta är ett permanent minne som används för att lagra data, operativsystem och program, till exempel en SSD eller hårddisk. Datorn använder också RAM. Detta är random access memory, som fungerar mycket snabbare jämfört med permanent minne. Slutligen har processorn ännu snabbare minnesblock, som tillsammans kallas cacher.

Om du tänker på en dators minne som en hierarki baserat på dess hastighet, skulle cachen vara överst i den hierarkin. Dessutom ligger den närmast beräkningskärnorna, eftersom den är en del av processorn.

Processorns cacheminne är ett statiskt minne (SRAM) och är utformat för att påskynda arbetet med RAM. Till skillnad från DRAM (Dynamic Random Access Memory) kan den lagra data utan att ständigt uppdatera den.

Hur fungerar processorcachen?

Som du kanske redan vet är ett program en uppsättning instruktioner som processorn kör. När du kör ett program måste datorn överföra dessa instruktioner från det permanenta minnet till processorn. Det är här minneshierarkin kommer in i bilden. Först laddas data in i RAM och överförs sedan till processorn.

Nuförtiden kan en processor bearbeta ett stort antal instruktioner per sekund. För att få ut det mesta av sina möjligheter behöver processorn supersnabbt minne. Det var därför cachen utvecklades.

Processorns minneskontroller gör jobbet med att hämta data från RAM och skicka den till cachen. Beroende på vilken processor som används i ditt system, kan denna styrenhet vara placerad i nordbryggan på moderkortet eller i själva processorn. Cachen lagrar också resultaten av att utföra instruktioner i processorn. Dessutom har själva processorcachen också sin egen hierarki.

Processorcachenivåer - L1, L2 och L3

Processorns cacheminne är uppdelat i tre nivåer: L1, L2 och L3. Denna hierarki är också baserad på cachens hastighet, såväl som dess storlek.

  • L1-cache (cache på första nivån)– Det här är den snabbaste typen av cache i processorn. När det gäller åtkomstprioritet innehåller denna cache de data som programmet kan behöva för att exekvera en specifik instruktion;
  • L2-cache (processorns andra nivås cache)- långsammare jämfört med L1, men större i storlek. Dess volym kan vara från 256 kilobyte till åtta megabyte. L2-cachen innehåller data som processorn kan behöva i framtiden. De flesta moderna processorer har L1- och L2-cache på själva processorkärnorna, där varje kärna får sin egen cache;
  • L3-cache (cache på tredje nivån)- det här är den största och långsammaste cachen. Dess storlek kan variera från 4 till 50 megabyte. I moderna processorer tilldelas ett separat utrymme på chippet för L3-cachen.

För tillfället är dessa alla processorcachenivåer; Intel försökte skapa en L4-cache, men denna teknik har ännu inte slagit rot.

Vad är cachen i processorn till för?

Det är dags att svara på huvudfrågan i den här artikeln: vad påverkar processorcachen? Data flödar från RAM till L3-cache, sedan till L2 och sedan till L1. När processorn behöver data för att utföra en operation försöker den hitta den i L1-cachen och om den hittar den kallas denna situation för en cacheträff. Annars fortsätter uppslagningen i L2- och L3-cachen. Om data fortfarande inte kan hittas görs en begäran till RAM-minnet.

Vi vet nu att cachen är utformad för att påskynda överföringen av information mellan RAM och processorn. Den tid som krävs för att hämta data från minnet kallas latens. L1-cache har lägst latens, så det är snabbast, L3-cache har högst. När det inte finns någon data i cachen upplever vi ännu högre latens då processorn behöver komma åt minnet.

Tidigare, i designen av processorer, flyttades L2- och L3-cachen utanför processorn, vilket ledde till höga latenser. Men genom att minska tillverkningsprocessen som används för att tillverka processorer kan miljarder transistorer placeras på ett mycket mindre utrymme än tidigare. Som ett resultat frigörs utrymme för att placera cachen så nära kärnorna som möjligt, vilket ytterligare minskar latensen.

Hur påverkar cachen prestanda?

Effekten av en cache på en dators prestanda beror direkt på dess effektivitet och antalet cacheträffar. Situationer när det inte finns några data i cachen minskar den totala prestandan avsevärt.

Föreställ dig att processorn laddar data från L1-cachen 100 gånger i rad. Om cacheträfffrekvensen är 100 % kommer det att ta processorn 100 nanosekunder att hämta denna data. Men så snart träfffrekvensen minskar till 99 % kommer processorn att behöva hämta data från L2-cachen, och det finns redan en fördröjning på 10 nanosekunder. Resultatet är 99 nanosekunder för 99 förfrågningar och 10 nanosekunder för 1 förfrågan. En minskning av cacheträffprocenten med 1 % minskar därför processorns prestanda med 10 %.

I realtid är cacheträfffrekvensen mellan 95 och 97 %. Men som du förstår är skillnaden i prestanda mellan dessa indikatorer inte 2%, utan 14%. Tänk på att i exemplet antar vi att den förlåtna datan alltid finns i L2-cachen, i verkligheten kan informationen vräkas från cachen, vilket innebär att den måste hämtas från RAM, som har en latens på 80- 120 nanosekunder. Här är skillnaden mellan 95 och 97 procent ännu mer betydande.

Dålig cacheprestanda i AMD Bulldozer- och Piledriver-processorer var en av huvudorsakerna till att de förlorade mot Intel-processorer. I dessa processorer delades L1-cachen mellan flera kärnor, vilket gjorde den väldigt ineffektiv. Moderna Ryzen-processorer har inte detta problem.

Vi kan dra slutsatsen att ju större cachestorlek, desto högre prestanda, eftersom processorn kommer att kunna få den data den behöver snabbare i fler fall. Det är dock värt att uppmärksamma inte bara storleken på processorcachen utan också dess arkitektur.

Slutsatser

Nu vet du vad processorcachen ansvarar för och hur den fungerar. Cache-designen utvecklas ständigt, och minnet blir snabbare och billigare. AMD och Intel har redan gjort många experiment med cache, och Intel försökte till och med använda L4-cache. Processormarknaden växer snabbare än någonsin. Cache-arkitekturen kommer att hålla jämna steg med den ständigt ökande kraften hos processorer.

Dessutom görs mycket för att eliminera de flaskhalsar som moderna datorer har. Att minska minneslatens är en av de viktigaste delarna av detta arbete. Framtiden ser mycket lovande ut.

Relaterade inlägg.

Nästan alla utvecklare vet att processorcachen är ett litet men snabbt minne som lagrar data från nyligen besökta minnesområden – definitionen är kort och ganska exakt. Men att känna till de tråkiga detaljerna om cachemekanismerna är nödvändigt för att förstå de faktorer som påverkar kodprestandan.

I den här artikeln kommer vi att titta på ett antal exempel som illustrerar olika funktioner hos cacher och deras inverkan på prestanda. Exemplen kommer att vara i C#, valet av språk och plattform påverkar inte i någon större utsträckning prestationsbedömningen och slutsatserna. Naturligtvis, inom rimliga gränser, om du väljer ett språk där läsning av ett värde från en array motsvarar att komma åt en hashtabell, kommer du inte att få några tolkningsbara resultat. Översättarens anteckningar är i kursiv stil.

Habracut - - -

Exempel 1: Minnesåtkomst och prestanda

Hur mycket snabbare tror du att den andra cykeln är än den första?
int arr = ny int;

// först
för (int i = 0; i< arr.Length; i++) arr[i] *= 3;

// sekund
för (int i = 0; i< arr.Length; i += 16) arr[i] *= 3;


Den första slingan multiplicerar alla värden i arrayen med 3, den andra slingan multiplicerar bara vart sextonde värde. Den andra cykeln slutförs bara 6% jobbar den första cykeln, men på moderna maskiner exekveras båda cyklerna på ungefär lika lång tid: 80 ms Och 78 ms respektive (på min maskin).

Lösningen är enkel - minnesåtkomst. Hastigheten för dessa slingor bestäms i första hand av hastigheten hos minnesdelsystemet och inte av hastigheten för heltalsmultiplikation. Som vi kommer att se i nästa exempel är antalet åtkomster till RAM detsamma i både det första och andra fallet.

Exempel 2: Inverkan av cachelinjer

Låt oss gräva djupare och prova andra stegvärden, inte bara 1 och 16:
för (int i = 0; i< arr.Length; i += K /* шаг */ ) arr[i] *= 3;

Här är körtiderna för denna loop för olika stegvärden K:

Observera att med stegvärden från 1 till 16 förblir drifttiden praktiskt taget oförändrad. Men med värden större än 16 minskar körtiden med ungefär hälften varje gång vi dubblar steget. Det betyder inte att loopen på något magiskt sätt börjar springa snabbare, bara att antalet iterationer också minskar. Nyckelpunkten är samma drifttid med stegvärden från 1 till 16.

Anledningen till detta är att moderna processorer inte kommer åt minnet en byte i taget, utan snarare i små block som kallas cache-linjer. Vanligtvis är strängstorleken 64 byte. När du läser något värde från minnet kommer minst en cache-rad in i cachen. Efterföljande åtkomst till valfritt värde från den här raden är mycket snabb.

Eftersom 16 int-värden upptar 64 byte, kommer loopar med steg från 1 till 16 åt samma antal cache-rader, eller mer exakt, alla cache-linjer i arrayen. Vid steg 32 sker åtkomst till varannan rad, vid steg 64 till var fjärde.

Att förstå detta är mycket viktigt för vissa optimeringstekniker. Antalet åtkomster till den beror på var data finns i minnet. Till exempel kan ojusterade data kräva två åtkomster till huvudminnet istället för en. Som vi fick reda på ovan kommer driftshastigheten att vara två gånger lägre.

Exempel 3: Nivå 1 och 2 cachestorlekar (L1 och L2)

Moderna processorer har vanligtvis två eller tre nivåer av cacher, vanligtvis kallade L1, L2 och L3. För att ta reda på storlekarna på cacher på olika nivåer kan du använda verktyget CoreInfo eller Windows API-funktionen GetLogicalProcessorInfo. Båda metoderna ger också information om cache-radstorleken för varje nivå.

På min dator rapporterar CoreInfo 32 KB L1-datacacher, 32 KB L1-instruktionscacher och 4 MB L2-datacacher. Varje kärna har sina egna personliga L1-cacher, L2-cacher delas av varje par av kärnor:

Logisk processor till cachekarta: *--- Data Cache 0, Nivå 1, 32 KB, Assoc 8, LineSize 64 *--- Instruktionscache 0, Nivå 1, 32 KB, Assoc 8, LineSize 64 -*-- Data Cache 1, Nivå 1, 32 KB, Assoc 8, LineSize 64 -*-- Instruktionscache 1, Nivå 1, 32 KB, Assoc 8, LineSize 64 **-- Unified Cache 0, Nivå 2, 4 MB, Assoc 16, LineSize 64 --*- Data Cache 2, Nivå 1, 32 KB, Assoc 8, LineSize 64 --*- Instruktionscache 2, Nivå 1, 32 KB, Assoc 8, LineSize 64 ---* Data Cache 3, Nivå 1, 32 KB, Assoc 8, LineSize 64 ---* Instruktionscache 3, Level 1, 32 KB, Assoc 8, LineSize 64 --** Unified Cache 1, Level 2, 4 MB, Assoc 16, LineSize 64
Låt oss kontrollera denna information experimentellt. För att göra detta, låt oss gå igenom vår array och öka vart 16:e värde - ett enkelt sätt att ändra data i varje cache-rad. När vi når slutet återvänder vi till början. Låt oss kolla olika arraystorlekar; vi bör se en minskning i prestanda när arrayen inte längre passar in i cacher på olika nivåer.

Koden är:

int steg = 64 * 1024 * 1024; // antal iterationer
int lengthMod = arr.Length - 1; // array size -- power of two

för (int i = 0; i< steps; i++)
{
// x & lengthMod = x % arr.Längd, eftersom två potenser
arr[(i * 16) & lengthMod]++;
}


Testresultat:

På min maskin är det märkbara sänkningar i prestanda efter 32 KB och 4 MB - det här är storlekarna på L1- och L2-cachen.

Exempel 4: Instruktionsparallelism

Låt oss nu titta på något annat. Enligt din åsikt, vilken av dessa två loopar kommer att köras snabbare?
int steg = 256 * 1024 * 1024;
int a = ny int ;

// först
för (int i = 0; i< steps; i++) { a++; a++; }

// sekund
för (int i = 0; i< steps; i++) { a++; a++; }


Det visar sig att den andra slingan går nästan dubbelt så snabbt, åtminstone på alla maskiner jag testat. Varför? Eftersom kommandon inuti loopar har olika databeroende. De första kommandona har följande kedja av beroenden:

I den andra cykeln är beroenden:

De funktionella delarna av moderna processorer är kapabla att utföra ett visst antal vissa operationer samtidigt, vanligtvis inte ett särskilt stort antal. Till exempel är parallell åtkomst till data från L1-cachen vid två adresser möjlig, och samtidig exekvering av två enkla aritmetiska instruktioner är också möjlig. I den första cykeln kan processorn inte använda dessa funktioner, men det kan den i den andra.

Exempel 5: Cacheassociativitet

En av nyckelfrågorna som måste besvaras när man designar en cache är om data från en viss minnesregion kan lagras i valfria cacheceller eller bara i några av dem. Tre möjliga lösningar:
  1. Direct Mapping Cache,Datan för varje cache-rad i RAM-minnet lagras endast på en fördefinierad cacheplats. Det enklaste sättet att beräkna mappningen är: row_index_in_memory % number_of_cache_cells. Två rader mappade till samma cell kan inte finnas i cachen samtidigt.
  2. N-entry partiell-associativ cache, kan varje rad lagras på N olika cacheplatser. Till exempel, i en cache med 16 poster, kan en rad lagras i en av de 16 celler som utgör gruppen. Vanligtvis delar rader med lika minst signifikanta bitar av index en grupp.
  3. Helt associativ cache, kan vilken rad som helst lagras på valfri cacheplats. Lösningen är likvärdig med en hashtabell i sitt beteende.
Direktmappade cacher är benägna att strida, till exempel när två rader tävlar om samma cell, växelvis vräker varandra från cachen, är effektiviteten mycket låg. Å andra sidan är helt associativa cacher, även om de är fria från denna nackdel, mycket komplexa och dyra att implementera. Delvis associativa cacher är en typisk kompromiss mellan implementeringskomplexitet och effektivitet.

Till exempel, på min maskin, är 4 MB L2-cache en 16-poster partiell-associativ cache. Hela RAM-minnet är uppdelat i uppsättningar linjer enligt de minst signifikanta bitarna av deras index, linjer från varje uppsättning tävlar om en grupp med 16 L2-cacheceller.

Eftersom L2-cachen har 65 536 celler (4 * 2 20 / 64) och varje grupp består av 16 celler, har vi totalt 4 096 grupper. Således bestämmer de nedre 12 bitarna av radindexet vilken grupp denna rad tillhör (2 12 = 4 096). Som ett resultat delar rader med adresser som är multiplar av 262 144 (4 096 * 64) samma grupp med 16 celler och tävlar om utrymmet i den.

För att effekterna av associativitet ska få effekt måste vi ständigt komma åt ett stort antal rader från samma grupp, till exempel genom att använda följande kod:

offentlig statisk lång UpdateEveryKthByte(byte arr, int K)
{
const int rep = 1024 * 1024; // antal iterationer

Stoppur sw = Stopwatch.StartNew();

int p = 0;
för (int i = 0; i< rep; i++)
{
arr[p]++;

P+= K; om (p >= arr.Längd) p = 0;
}

Sw.Stop();
returnera sw.ElapsedMilliseconds;
}


Metoden ökar varje K:te element i arrayen. När vi når slutet börjar vi igen. Efter ett ganska stort antal iterationer (2 20) slutar vi. Jag gjorde körningar för olika arraystorlekar och K-stegvärden. Resultat (blå - lång körtid, vit - kort):

Blå områden motsvarar de fall där, med konstanta dataändringar, cachen inte kan ta emot alla nödvändiga uppgifter på en gång. En ljusblå färg indikerar en drifttid på cirka 80 ms, nästan vit - 10 ms.

Låt oss ta itu med de blå områdena:

  1. Varför visas vertikala linjer? Vertikala linjer motsvarar stegvärden där för många rader (mer än 16) från en grupp nås. För dessa värden kan min maskins cache med 16 poster inte ta emot all nödvändig data.

    Några av de dåliga stegvärdena är två potenser: 256 och 512. Överväg till exempel steg 512 och en 8 MB-array. Med detta steg finns det 32 ​​sektioner i arrayen (8 * 2 20 / 262 144), som tävlar med varandra om celler i 512 cachegrupper (262 144 / 512). Det finns 32 sektioner, men det finns bara 16 celler i cachen för varje grupp, så det finns inte tillräckligt med utrymme för alla.

    Andra stegvärden som inte är två potenser är helt enkelt oturliga, vilket orsakar ett stort antal träffar till samma cachegrupper och leder också till uppkomsten av vertikala blå linjer i figuren. Vid det här laget uppmanas älskare av talteori att tänka.

  2. Varför bryts vertikala linjer vid 4 MB-gränsen? När arraystorleken är 4 MB eller mindre, beter sig cachen med 16 poster som en helt associativ cache, det vill säga den kan rymma all data i arrayen utan konflikter. Det finns inte fler än 16 områden som slåss om en cachegrupp (262 144 * 16 = 4 * 2 20 = 4 MB).
  3. Varför finns det en stor blå triangel längst upp till vänster? För med ett litet steg och en stor array kan cachen inte få plats med all nödvändig data. Graden av cacheassociativitet spelar en sekundär roll här, begränsningen är relaterad till storleken på L2-cachen.

    Till exempel, med en arraystorlek på 16 MB och en stride på 128, kommer vi åt var 128:e byte, och modifierar därmed varannan arraycache-rad. För att lagra varannan rad i cachen behöver du 8 MB cache, men på min maskin har jag bara 4 MB.

    Även om cachen var helt associativ skulle den inte tillåta 8 MB data att lagras i den. Observera att i det redan diskuterade exemplet med ett steg på 512 och en arraystorlek på 8 MB, behöver vi bara 1 MB cache för att lagra all nödvändig data, men detta är omöjligt på grund av otillräcklig cacheassociativitet.

  4. Varför ökar den vänstra sidan av triangeln gradvis i intensitet? Den maximala intensiteten inträffar vid ett stegvärde på 64 byte, vilket är lika med storleken på cacheraden. Som vi såg i det första och andra exemplet kostar sekventiell åtkomst till samma rad nästan ingenting. Låt oss säga att med ett steg på 16 byte har vi fyra minnesåtkomster för priset av en.

    Eftersom antalet iterationer är detsamma i vårt test för valfritt stegvärde, resulterar ett billigare steg i mindre körtid.

De upptäckta effekterna kvarstår vid stora parametervärden:

Cacheassociativitet är en intressant sak som kan visa sig under vissa förutsättningar. Till skillnad från de andra problemen som diskuteras i den här artikeln är det inte så allvarligt. Det är definitivt inget som kräver konstant uppmärksamhet när man skriver program.

Exempel 6: Partitionering av falsk cache

På flerkärniga maskiner kan du stöta på ett annat problem - cachekoherens. Processorkärnor har delvis eller helt separata cacher. På min maskin är L1-cacharna separata (som vanligt), och det finns också två L2-cacher som delas av varje par kärnor. Detaljerna kan variera, men i allmänhet har moderna flerkärniga processorer hierarkiska cacher på flera nivåer. Dessutom tillhör de snabbaste, men också de minsta cacharna, individuella kärnor.

När en kärna modifierar ett värde i sin cache, kan andra kärnor inte längre använda det gamla värdet. Värdet i cachen för andra kärnor måste uppdateras. Dessutom måste den uppdateras hela cacheraden, eftersom cacher fungerar på data på radnivå.

Låt oss demonstrera detta problem med följande kod:

privat statisk int s_counter = ny int;

privat void UpdateCounter(int position)
{
för (int j = 0; j< 100000000; j++)
{
s_counter = s_counter + 3;
}
}


Om jag på min fyrkärniga maskin anropar den här metoden med parametrarna 0, 1, 2, 3 samtidigt från fyra trådar, kommer körtiden att vara 4,3 sekunder. Men om jag anropar metoden med parametrarna 16, 32, 48, 64, så blir körtiden endast 0,28 sekunder.

Varför? I det första fallet kommer sannolikt alla fyra värden som behandlas av trådar vid varje given tidpunkt att hamna i en cache-rad. Varje gång en kärna ökar ett värde, markerar den cacheceller som innehåller det värdet i andra kärnor som ogiltiga. Efter denna operation måste alla andra kärnor cachelagra raden igen. Detta gör cachningsmekanismen inoperabel, vilket dödar prestandan.

Exempel 7: Hårdvarukomplexitet

Även nu, när principerna för cachedrift inte är några hemligheter för dig, kommer hårdvaran fortfarande att ge dig överraskningar. Processorer skiljer sig från varandra i optimeringsmetoder, heuristik och andra implementeringsfinesser.

L1-cachen för vissa processorer kan komma åt två celler parallellt om de tillhör olika grupper, men om de tillhör samma grupp, bara sekventiellt. Såvitt jag vet kan vissa till och med komma åt olika håll i samma cell parallellt.

Processorer kan överraska dig med smarta optimeringar. Exempelvis fungerar inte koden från föregående exempel om falsk cachedelning på min hemdator som det är tänkt – i de enklaste fallen kan processorn optimera arbetet och minska negativa effekter. Modifierar du koden lite så faller allt på plats.

Här är ett annat exempel på konstiga hårdvaruquirks:

privat statisk int A, B, C, D, E, F, G;

privat statisk tomrum Konstighet()
{
för (int i = 0; i< 200000000; i++)
{
<какой-то код>
}
}


Om istället<какой-то код>Ersätt tre olika alternativ, du kan få följande resultat:

Att öka fälten A, B, C, D tar längre tid än att öka fälten A, C, E, G. Det som är ännu konstigare är att det tar längre tid att öka fälten A och C än fälten A, C Och E, G. Jag vet inte exakt vad orsakerna till detta är, men kanske är de relaterade till minnesbanker ( ja, ja, med vanliga tre-liters sparminnesbanker, och inte vad du trodde). Om du har några tankar om denna fråga, säg till i kommentarerna.

På min maskin observeras inte ovanstående, men ibland finns det onormalt dåliga resultat - troligtvis gör uppgiftsschemaläggaren sina egna "justeringar".

Lärdomen att dra av detta exempel är att det är mycket svårt att helt förutsäga hårdvarans beteende. Ja, Burk förutsäga mycket, men du måste hela tiden bekräfta dina förutsägelser genom mätningar och tester.

Slutsats

Jag hoppas att allt som diskuterats ovan har hjälpt dig att förstå strukturen för processorcacher. Nu kan du omsätta denna kunskap i praktiken för att optimera din kod.