Om te beginnen:
Excuses als het wat door elkaar loopt .. t'is niet eenvoudig om al deze info in een post te verwerken

Ik ga nog editeren waar nodig om het leesbaarder te maken ..
Start
"c" = clockcycles
Een eenvoudige instructie kost 1c (vb : "Add EAX, EBX" (register EBX optellen bij EAX)
Stel : De CPU heeft data nodig die niet in een register zit, en wil deze ophalen.
Dit gebeurd in onderstaande volgorde, maar het kost een aantal cycles dat de CPU dient te wachten alvorens de data klaar staat voor gebruik.
Dit noemt met een "stall"
1. Check L1 cache
Data aanwezig : 4c
2. Check L2 cache
Data aanwezig : 12c
3. Check L3 cache
Data aanwezig : 36c
4. Check RAM
Data aanwezig : ~120c
5. Check HDD
Ophaaltijde : 13ms (1000'en cycles)
Cache Hiërarchy
Cache latency
Haswell memory stall
Het kost dus ca 120c om een byte op te halen uit RAM
(64bytes in feite, maar ik kom hier later op terug)
Deze 120c is vb met 9-9-9-24 RAM
Maken we nu de vergelijking met RAM met strakke timings : 7-7-7-27
120 - ( (2 + 2 + 2) - 3) = 117C (2,5% lager)
Dit is ook ruwweg de gemiddelde winst die je ziet in de meeste benchmarks.
Waarom maakt dit meestal niets uit in de meeste software ?
Veel software is een onvoorspelbare mix van sequentiele & random access (zie hieronder)
De grootste tijd dat software data verwerkt, komt deze sequentieel binnen.
Slechts een kleinere fractie van het aantal reads gebeurd random.
Op een gegeven moment is de CPU verzadigd met data, en boek je geen winst meer met snellere RAM.
Bij sequentiele reads komt de magie van
Latency Hiding erbij en elimineert de nood aan strakke timings.
Wat is Latency Hiding ?
Bij een leesbewerking vanuit RAM, gebeurd dit in blokken gelijk aan de cache line size.
Zo goed als iedere x86 vanaf de pentium 1 heeft een cache line size van 64Bytes.
Dit betekent dus als ik 1 byte wil uit RAM, dat er
64 bytes gelezen worden in die 120 cycles.
Stel nu dat ik byte per byte aan het processen ben, en dit voor 128 bytes,
dan zal de CPU tijdens het processen van de eerste 64 bytes, reeds in parallel op de achtergrond de volgende 64 gaan ophalen
en op de volgende cache lijn plaatsen.
Wanneer ik byte 65 inlees,
is deze reeds aanwezig in cache, en verlies ik niet opnieuw 130 cycles, maar slechts 4(L1) .. 12(L2) of 36(L3).
In de praktijk zal dit meestal vanuit L1 zijn.
Zolang ik dus sequentieel data uitlees, zelfs al is dit verschillende gigabytes, zal de latency steeds maar 4 cycles zijn.
Het lijkt dus net alsof alle data uit de snelle cache lijkt te komen & die dure latency van 120c niet bestaat !!
In sommige BIOS'en zie je soms de optie "adjecent cache line read". Wat hier betekend dat de CPU altijd al de volgende lijn zal ophalen.

Tegenwoordig gebeurd dit op een arbitraire manier (enkel indien nodig) en dit wordt dynamisch ingeschat door de CPU.
Sequentieel:
- Ik vraag data op
-> CPU haalt 64 bytes uit RAM regio "A" (130c latency)
- Ik process 64 bytes
-> CPU haalt terwijl in parallel de volgende 64 bytes uit RAM (Regio "A" + 64)
- Ik process de volgende 64 bytes (4c latency)
-> CPU haalt terwijl in parallel de volgende 64 bytes uit RAM (Regio "A" + 128)
- Ik process de volgende 64 bytes (4c latency)
-> CPU haalt terwijl in parallel de volgende 64 bytes uit RAM (Regio "A" + 192)
- Ik process de volgende 64 bytes (4c latency)
enz.
Totale latency : (in c)
130 + 4 + 4 + 4 + 4 + 4 + 4 + 4 ........ 4 + 4 + 4 .... 4 + 4 + 4
- 1x 130c latency bij aanvang
- 4c latency per byte
Random read:
- Ik vraag data op
-> CPU haalt 64 bytes uit RAM regio A (130c latency)
- Ik process 64 bytes
-> CPU haalt terwijl in parallel de volgende 64 bytes uit RAM (Regio "A + 64")
- Dit is niet de gewenste data ! Ik wil de data uit regio "K"
-> CPU haalt 64 bytes op uit regio "K", terwijl ik 130c zit te niksen ..
Ik process de volgende 64 bytes (130c latency !)
-> CPU haalt terwijl in parallel de volgende 64 bytes uit RAM (Regio "K" + 64)
- Dit is niet de gewenste data ! Ik wil de data uit regio "Z"
-> CPU haalt 64 bytes op uit regio "Z", terwijl ik 130c zit te niksen ..
enz.
Totale latency : (in c)
130 + 4 + 4 + 4 + 4 + 4 + 4 + 4 ........ +
130 + 4 + 4 + 4 + 4 ... +
130
- 1x 130c latency bij aanvang
- 4c latency per byte
-
130c latency na elke 64 bytes
In dit voorbeeld lezen we byte per byte uit.
Dit betekend dat we 130c + 256c (4*64) nodig hebben om 1 cache lijn naar de CPU registers te kopieren.
Total : 130 (33%) + 256 (66%)
In de praktijk komen meestal integers voor (4 Bytes lang) die in 1 actie ingelezen worden naar een register (oftewel 4c * (64B / 4) = 64 cycles om 1 cache lijn uit te lezen
Total : 130 (66%) + 64 (33%)
Conclusies:
Bij sequentieel lezen maakt de RAM latency niets uit, zolang de volgende 64 bytes al opgehaald zijn, voor je klaar bent met de eerste 64 te verwerken.
Bij hevige random access, zal je tot ~6 cycles winst maken per 64 bytes .. reken zelf uit
Single, dual, quad channel:
-> 1 RAM channel is 64 bit breed. (8 bytes)
-> Er worden 64 bytes naar cache ingelezen per actie
Single : 64 bit
Dual : 128bit
Tripple : 192bit
Quad : 256bit
Om dus 1 cache line op te vullen dient fysiek volgend aantal reads gedaan te worden :
Single channel : 8 x 8B
Dual channel : 4 x 16B
Tripple channel : 3 x 24B (64 + 8 bytes)
Quad channel : 2 x 32B
Quad channel is dus 4x sneller dan single om 1 cache line op te vullen. (logisch ..)
Wanneer is Dual & Quad dan sneller ?
Sequentieel:
Als een CPU bij een bepaalde taak
je cache lines sneller verwerkt, dan dat een Single Channel opstelling ze kan vullen.
Dit is een typisch patroon bij videobewerking applicaties of data compressie.
Hier wordt een grote hoeveelheid data sequentieel gelezen, en zijn de bewerking op de data zelf meestal relatief licht.
Random:
Hier is de winst veel kleiner, daar je véél meer afhangt van de latency, dan van de bandbreedte om een cache line op te vullen.
Het mag vb 10c korter zijn om de lijn op te vullen, je verliest toch nog steeds 130c omwille van de latency.
Ook nog:
Bij veel applicaties merk je geen verschil tussen traag & snel geheugen.
Dit komt omdat de actiefst gebruikte dataset
past in de cache, en de CPU dus relatief weinig memory access nodig heeft.
Naar mijn mening zijn er 3 patronen die het meeste voorkomen :
- Sequentiele data, in grote hoeveelheden.
--> Grote kans op winst bij RAM met meer bandbreedte
--> Geen winst met strakkere timings
- Random data
--> Weinig tot geen winst met hogere RAM bandbreedte.
--> Kleine winst met strakkere timings
- Mix, maar kleine dataset (Meeste data komt steeds uit cache, relatief weinig memory access)
--> Kleine kans tot winst met hogere RAM bandbreedte (hangt van CPU cache grootte)
--> Geen winst met strakkere timings
Als extra:
Data kan ook met SSE(2) of AVX(2) ingeladen worden.
Dit houd in dat ik 128b (16Byte) of 256b (32Byte) aan data in 2 .. 3 cycles kan inladen vanuit cache. (aligned read)
Dit betekend dat ik in 8 tot 16 cycles een volledige cache lijn kan inlezen naar de registers.
De aandachtige lezer ziet waarschijnlijk al in dat je een serieuze geheugen bandbreedte nodig hebt om dit tempo van de CPU bij te benen. (en dat strakke timings niet uitmaakt in dit geval)
Ook is het geen toeval dat een SSE load instructie 16 bytes inleest per actie, en dat Dual Channel DDR 16 bytes naar cache stuurt per actie.
Het is dan ook geen toeval dat technieken zoals DDR (tov SDR RAM) en Dual Channel relatief snel geïntroduceerd zijn na introductie van de SSE & SSE2 instructiesets.
Toen werd al snel duidelijk dat single channel SDR RAM de bottleneck begon te worden bij grote sequentiele datavolumes, of kopieeractie in memory
Wanneer is je RAM snel genoeg ?
-> Vanaf je processor geen enkele clockcycle meer verliest door te wachten op een RAM-fetch, en dit met een load van z'n snelste load-instructie. (SSE(2) of AVX(2) register.
Stel dat een CPU exact 100c nodig heb om 1 cache line (64B) uit te lezen en te verwerken, en dat een single channel DDR3 lat 200c nodig heeft om 64B toe te leveren.
De helft van de tijd staat de CPU te "stallen" op input vanuit je RAM.
Switchen we naar Dual Channel, lopen beide gelijk. Je RAM biedt net voldoende bandbreedte om de CPU niet te laten "stallen". Maar gaan we over naar Quad Channel .. is de CPU de bottleneck, en win je niets (RAM heeft maar 50c nodig per line hier, de CPU 100c). Je RAM staat dus te wachten.
Let wel .. dit is pure theorie.
In de praktijk gaat een CPU altijd héél wat instructie uitvoeren op een portie data.
Veel reviewsites tonen aan dat sneller dan DDR 1600 niet echt nuttig is bij games (met discrete kaart).
Dit komt omdat de stalls tot een minimum herleidt zijn, en dus sneller geen zin heeft.
Conclusie :
- Vanaf dat je CPU verzadigd is, en de memory-stall's tot een minimum herleid zijn, win je 0% performance door sneller geheugen te gebruiken.
- De manier hoe een applicatie data verwerkt (groot ? sequentieel ?) geeft de user al een aardige hint of méér, sneller en/of strakker geheugen invloed zal hebben.
- De grote caches tegenwoordig, ingebouwd in de CPU, zijn meestal meer dan groot genoeg om de drukste datasets van een applicatie volledig te kunnen bevatten, wat het aantal memory transacties dramatisch reduceert.
Als afsluiter : Leuk weetje 
Intel kondigde geruime tijd geleden aan dat AVX-512 op de planning stond.
Stel je dit voor : 1
volledige cache lijn uitlezen in 2 tot 3 clockcycles !
Daar staan we dan vandaag met onze Quad Channel DDR3 .. die al 2 reads moet uitvoeren om 1 lijn op te vullen ....
en dan gebruiken we nog maar !! 1 !! core .......
[Reactie gewijzigd door xback op 22 juli 2024 19:43]