Programmering

Hvordan øke hastigheten på koden din ved hjelp av CPU-hurtigbuffere

CPU-hurtigbufferen reduserer minnetiden når data er tilgjengelig fra hovedminnet til systemet. Utviklere kan og bør benytte seg av CPU-cache for å forbedre applikasjonsytelsen.

Hvordan CPU-cacher fungerer

Moderne CPU-er har vanligvis tre nivåer med hurtigbuffer, merket L1, L2 og L3, som gjenspeiler rekkefølgen som CPU-en sjekker dem i. CPUer har ofte en datacache, en instruksjonsbuffer (for kode) og en enhetlig cache (for hva som helst). Å få tilgang til disse hurtigbufferne er mye raskere enn tilgang til RAM: Vanligvis er L1-hurtigbufferen omtrent 100 ganger raskere enn RAM for datatilgang, og L2-hurtigbufferen er 25 ganger raskere enn RAM for datatilgang.

Når programvaren din kjører og trenger å hente inn data eller instruksjoner, blir CPU-cachene først sjekket, deretter tregere system-RAM, og til slutt de mye tregere diskstasjonene. Derfor vil du optimalisere koden din for å finne det som sannsynligvis trengs fra CPU-hurtigbufferen først.

Koden din kan ikke spesifisere hvor datainstruksjoner og data ligger - maskinvaren gjør det - så du kan ikke tvinge visse elementer inn i CPU-hurtigbufferen. Men du kan optimalisere koden din for å hente størrelsen på L1-, L2- eller L3-hurtigbufferen i systemet ditt ved hjelp av Windows Management Instrumentation (WMI) for å optimalisere når applikasjonen din får tilgang til hurtigbufferen og dermed ytelsen.

CPUer får aldri tilgang til hurtigbufferbyte for byte. I stedet leser de minnet i cache-linjer, som er deler av minnet, vanligvis 32, 64 eller 128 byte i størrelse.

Følgende kodeliste illustrerer hvordan du kan hente L2 eller L3 CPU-cache-størrelse i systemet ditt:

offentlig statisk uint GetCPUCacheSize (streng cacheType) {prøv {å bruke (ManagementObject managementObject = new ManagementObject ("Win32_Processor.DeviceID = 'CPU0'")) {return (uint) (managementObject [cacheType]); }} fange {retur 0; }} statisk ugyldig 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 tilleggsdokumentasjon om Win32_Processor WMI-klassen.

Programmering for ytelse: Eksempelkode

Når du har gjenstander i bunken, er det ikke noe søppeloppsamling overhead. Hvis du bruker haugbaserte objekter, er det alltid en kostnad forbundet med generasjonssøppeloppsamlingen for å samle eller flytte gjenstander i haugen eller komprimere haugminnet. En god måte å unngå søppeloppsamling overhead er å bruke structs istedenfor klasser.

Cacher fungerer best hvis du bruker en sekvensiell datastruktur, for eksempel en matrise. Sekvensiell bestilling lar CPU-en lese videre og også lese fremover spekulativt i påvente av hva som sannsynligvis blir bedt om neste. Dermed er en algoritme som får tilgang til minnet sekvensielt alltid rask.

Hvis du får tilgang til minne i tilfeldig rekkefølge, trenger CPU nye cachelinjer hver gang du får tilgang til minne. Det reduserer ytelsen.

Følgende kodebit implementerer et enkelt program som illustrerer fordelene ved å bruke en struktur over en klasse:

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

Følgende kode profilerer ytelsen til å bruke en rekke strukturer mot en rekke klasser. For illustrasjonsformål har jeg brukt en million objekter til begge deler, men du trenger vanligvis ikke så mange objekter i applikasjonen din.

statisk tomrom Main (streng [] args) {const int size = 1000000; var structs = new RectangleStruct [størrelse]; var klasser = ny RectangleClass [størrelse]; var sw = nytt stoppeklokke (); sw.Start (); for (var i = 0; i <størrelse; ++ i) {structs [i] = ny RectangleStruct (); structs [i] .bredde = 0 structs [i]. høyde = 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øyde = 0; } var classTime = sw.ElapsedMilliseconds; sw.Stop (); Console.WriteLine ("Tid tatt av matrisen av klasser:" + classTime.ToString () + "millisekunder."); Console.WriteLine ("Time taken by array of structs:" + structTime.ToString () + "milliseconds."); Console.Read (); }

Programmet er enkelt: Det lager 1 million gjenstander av strukturer og lagrer dem i en matrise. Det oppretter også 1 million objekter i en klasse og lagrer dem i en annen matrise. Eiendommenes bredde og høyde er tildelt en verdi på null for hver forekomst.

Som du kan se, gir cache-vennlige strukturer en enorm ytelsesgevinst.

Tommelfingerregler for bedre bruk av CPU-cache

Så hvordan skriver du kode som best bruker CPU-hurtigbufferen? Dessverre er det ingen magisk formel. Men det er noen tommelfingerregler:

  • Unngå å bruke algoritmer og datastrukturer som viser uregelmessige minnetilgangsmønstre; bruk lineære datastrukturer i stedet.
  • Bruk mindre datatyper og organiser dataene slik at det ikke er noen justeringshull.
  • Vurder tilgangsmønstrene og dra nytte av lineære datastrukturer.
  • Forbedre romlig lokalitet, som bruker hver hurtigbuffelinje i størst mulig grad når den er tilordnet en hurtigbuffer.
$config[zx-auto] not found$config[zx-overlay] not found