Programmering

Sådan fremskyndes din kode ved hjælp af CPU-caches

CPU's cache reducerer hukommelsestiden, når der er adgang til data fra hovedsystemets hukommelse. Udviklere kan og bør udnytte CPU-cache for at forbedre applikationsydelsen.

Sådan fungerer CPU-caches

Moderne CPU'er har typisk tre niveauer af cache, mærket L1, L2 og L3, hvilket afspejler rækkefølgen, i hvilken CPU kontrollerer dem. CPU'er har ofte en datacache, en instruktionscache (til kode) og en samlet cache (til noget). Adgang til disse caches er meget hurtigere end adgang til RAM: L1-cache er typisk ca. 100 gange hurtigere end RAM for dataadgang, og L2-cache er 25 gange hurtigere end RAM for dataadgang.

Når din software kører og har brug for at hente data eller instruktioner, kontrolleres CPU-cacherne først, derefter det langsommere system-RAM og endelig de meget langsommere diskdrev. Derfor vil du først optimere din kode for at finde det, der sandsynligvis er nødvendigt fra CPU-cachen.

Din kode kan ikke angive, hvor datainstruktioner og data findes - computerens hardware gør det - så du kan ikke tvinge visse elementer ind i CPU-cachen. Men du kan optimere din kode for at hente størrelsen på L1, L2 eller L3 cache i dit system ved hjælp af Windows Management Instrumentation (WMI) for at optimere, når din applikation får adgang til cachen og dermed dens ydeevne.

CPU'er har aldrig adgang til cache-byte for byte. I stedet læser de hukommelsen i cache-linjer, som generelt er 32, 64 eller 128 bytes størrelse.

Følgende kodeliste illustrerer, hvordan du kan hente L2 eller L3 CPU-cache-størrelse i dit system:

offentlig statisk uint GetCPUCacheSize (streng cacheType) {prøv {ved hjælp af (ManagementObject managementObject = new ManagementObject ("Win32_Processor.DeviceID = 'CPU0'")) {return (uint) (managementObject [cacheType]); }} fange {retur 0; }} statisk ugyldigt Main (string [] args) {uint L2CacheSize = GetCPUCacheSize ("L2CacheSize"); uint L3CacheSize = GetCPUCacheSize ("L3CacheSize"); Console.WriteLine ("L2CacheSize:" + L2CacheSize.ToString ()); Console.WriteLine ("L3CacheSize:" + L3CacheSize.ToString ()); Console.Read (); }

Microsoft har yderligere dokumentation om W32-klassen Win32_Processor.

Programmering til performance: Eksempel kode

Når du har objekter i stakken, er der ikke noget affaldsindsamlingsomkostninger. Hvis du bruger bunkebaserede objekter, er der altid en omkostning forbundet med generationens affaldssamling til at samle eller flytte genstande i bunken eller komprimere bunkehukommelsen. En god måde at undgå affaldsindsamling på er at bruge structs i stedet for klasser.

Caches fungerer bedst, hvis du bruger en sekventiel datastruktur, f.eks. En matrix. Sekventiel rækkefølge lader CPU'en læse fremad og også læse fremad spekulativt i forventning om, hvad der sandsynligvis vil blive anmodet om næste gang. Således er en algoritme, der får adgang til hukommelsen sekventielt, altid hurtig.

Hvis du får adgang til hukommelse i tilfældig rækkefølge, har CPU'en brug for nye cachelinjer, hver gang du får adgang til hukommelse. Det reducerer ydeevnen.

Følgende kodestykke implementerer et simpelt program, der illustrerer fordelene ved at bruge en struct over en klasse:

 struct RectangleStruct {public int bredde; offentlig int højde; } klasse RectangleClass {public int bredde; offentlig int højde; }

Den følgende kode profilerer ydeevnen ved at bruge en række strutter mod en række klasser. Til illustration har jeg brugt en million objekter til begge dele, men du har typisk ikke brug for så mange objekter i din applikation.

statisk ugyldigt Main (string [] args) {const int size = 1000000; var structs = new RectangleStruct [størrelse]; var klasser = ny RectangleClass [størrelse]; var sw = nyt stopur (); sw.Start (); for (var i = 0; i <størrelse; ++ i) {structs [i] = ny RectangleStruct (); structs [i] .bredde = 0 structs [i]. højde = 0; } var structTime = sw.ElapsedMilliseconds; sw.Reset (); sw.Start (); for (var i = 0; i <størrelse; ++ i) {klasser [i] = ny RectangleClass (); klasser [i] .bredde = 0; klasser [i] .højde = 0; } var classTime = sw.ElapsedMilliseconds; sw.Stop (); Console.WriteLine ("Tid taget af matrix af klasser:" + classTime.ToString () + "millisekunder."); Console.WriteLine ("Tid taget af array af structs:" + structTime.ToString () + "millisekunder."); Console.Read (); }

Programmet er simpelt: Det opretter 1 million objekter af strukturer og gemmer dem i en matrix. Det opretter også 1 million objekter i en klasse og gemmer dem i et andet array. Egenskabernes bredde og højde tildeles en værdi på nul for hver forekomst.

Som du kan se, giver brug af cache-venlige strukturer en enorm præstationsgevinst.

Tommelfingerregler for bedre CPU-cache-brug

Så hvordan skriver du kode, der bedst bruger CPU-cachen? Desværre er der ingen magisk formel. Men der er nogle tommelfingerregler:

  • Undgå at bruge algoritmer og datastrukturer, der viser uregelmæssige hukommelsesadgangsmønstre; brug i stedet lineære datastrukturer.
  • Brug mindre datatyper og organiser dataene, så der ikke er nogen justeringshuller.
  • Overvej adgangsmønstre og drage fordel af lineære datastrukturer.
  • Forbedre rumlig lokalitet, som bruger hver cache-linje i det maksimale omfang, når den er blevet kortlagt til en cache.
$config[zx-auto] not found$config[zx-overlay] not found