Optimaliseer virtueel geheugen en cache-instellingen. Hoe u de RAM-prestaties kunt optimaliseren

Het meest sterke kwaliteiten RAM is de hoge snelheid waarmee informatie wordt gelezen en geschreven. Helaas wordt dit alleen bereikt vanwege de fysieke eigenschappen en de kleine omvang van geheugenmodules.

Als u de gegevens echter “correct” in het geheugen schrijft en daar onnodige informatie verwijdert, kan niets u ervan weerhouden ervan te genieten hoge snelheden RAM-prestaties:

  • Met het hulpprogramma Dataram RAMDisk kunt u een deel van uw RAM als harde schijf gebruiken.
  • Onze stap-voor-stap instructies hieronder " Een vrije RAMDisk instellen» wij laten zien hoe u kunt creëren en aanpassen Niet groot formaat flashdrive in RAM. Over hoe je zo’n schijf kunt gebruiken om bijvoorbeeld een cache op te slaan Firefox-browser, zullen we u vertellen in de onderstaande instructies “ We plaatsen de browsercache op de RAM-schijf».
  • Met het gratis CleanMEM-programma kunt u automatisch of handmatig niet langer benodigde gegevens uit uw RAM verwijderen.

Het vergroten van de RAM-grootte

Als uw computer ondanks al uw optimalisatie-inspanningen traag blijft aanvoelen, zijn er nog een paar andere dingen die u kunt doen om de systeemprestaties te verbeteren. Om dit te doen, moet u de reeds geïnstalleerde geheugenmodules vervangen of nieuwe toevoegen. Hoewel u hiervoor uw computerbehuizing moet demonteren, helpen wij u graag gedetailleerde instructies en foto's.

  • We praten over hoe u het type RAM dat op uw computer wordt gebruikt, kunt identificeren en hoe u het aantal vrije slots kunt achterhalen. Zo kom je erachter welk RAM-geheugen en hoeveel er in jouw geval geïnstalleerd kan worden.
  • We geven u ook instructies over hoe u nieuw RAM-geheugen kunt installeren.
  • In het geval van laptops: vervanging individuele componenten is misschien iets ingewikkelder. Het is echter wel zo harde schijven en geheugenmodules zijn in de regel toegankelijk via speciale "servicedeuren" die opengaan zonder dat u de behuizing volledig hoeft te demonteren. In dit artikel geven wij u.
  • Let op de garantievoorwaarden op uw computer - of deze verloren gaat als de behuizing wordt geopend. Als u twijfelt, kunt u beter een specialist inhuren om componenten te installeren of te vervangen.

We plaatsen de browsercache op de RAM-schijf:

Een vrije RAMDisk instellen

1 Gratis hulpprogramma


Met Starwind RAMDisk kunt u gratis een of meer RAM-schijven voor Windows instellen. De enige beperking: de grootte van elke dergelijke RAM-schijf mag niet groter zijn dan 1 GB.

2 Heeft misschien een chauffeur nodig


Windows zou moeten denken dat we het in het geval van een RAM-schijf over een harde schijf hebben. Dit software bereikt met de hulp van een nieuwe chauffeur.

3 Beoordeling van RAM-schijven


Windows kan zelfs met meerdere RAM-schijven werken. Het hulpprogramma geeft u een overzicht van alle RAM-schijven die beschikbaar zijn in het systeem.

4 Een RAM-schijf toevoegen


Om een ​​nieuwe RAM-schijf toe te voegen en te configureren, klikt u op het item "Apparaat toevoegen".

5 Een assistent helpt u bij de installatie


Een ‘assistent’ komt u direct te hulp en helpt u bij het doorlopen van de belangrijkste stappen.

6 Limiet van 1 GB


In het geval van een vrij gedistribueerde versie van het programma mag de grootte van de RAM-schijf niet groter zijn dan 1 GB. In de meeste gevallen zou dit echter voldoende moeten zijn. U kunt verschillende RAM-schijven gebruiken voor verschillende acties. Belangrijk: zorg ervoor dat u het vakje naast “Dit apparaat automatisch koppelen” aanvinkt, zodat elke keer dat het systeem opnieuw wordt opgestart, de RAM-schijf automatisch in Verkenner verschijnt.

Windows is gebouwd om door zoveel mogelijk machines met verschillende configuraties te worden gebruikt en is daarom doorgaans niet geoptimaliseerd voor uw computer en gebruik. Ik nodig u ook uit om onze andere artikelen over het optimaliseren van uw computers te lezen als u dat al heeft gedaan. “Geen optimalisatie”, dit is op processorniveau. Onze processors hebben caches waarmee ze bepaalde gegevens in het geheugen kunnen opslaan om het herstel van hun gegevens in de toekomst te versnellen: http://fr.wikipedia.org/wiki/M%C3%A9moire_cache. Volgens de processors kun je 2 of 3 niveaus ( )) cache. Door Windows-standaard gebruikt een L2-cache (niveau 2) van 256 KB, wat betekent dat als uw processor meer ruimte kan gebruiken (wat meestal gebeurt en langer zal duren gezien de constante evolutie van processors), Windows wordt beperkt door de mogelijkheden van uw processor! En ik zal niet eens zeggen dat er geen L3-cache is, omdat Windows deze niet gebruikt, het is alsof je die derde cache niet hebt, de grootste van de 3 niveaus op de processor!

De processorinformatie:

Het eerste dat u moet doen, is de mogelijkheden van de CPU (processor) kennen, dit is:

  • CPU downloaden – Z:-http://www.cpuid.com/softwares/cpu-z.html
  • Installeer het en open het
  • Om de verschillende cacheniveaus te zien, heeft u 2 opties: 1 tabblad rechtsonder of 2

Om de L2-cache in Windows te vergroten:

  1. Zoek en open vanuit het menu Start "regedit" (registerdatabase) (zoals bij elke manipulatie van de registerdatabase wordt aanbevolen een back-up van uw computer te maken in geval van problemen)
  2. Dubbelklik op HKEY_LOCAL_MACHINE > Systeem > CurrentControlSet > Besturingselementen > Sessiebeheer > Geheugenbeheer
  3. in het rechtervenster zou je een sleutel moeten vinden met de naam "SecondLevelDataCache", klik erop klik met de rechtermuisknop muis erop en klik op "Bewerken"
  4. Klik op de knop "Decimaal".
  5. En vervangen WAAR uw verwerker. In mijn geval vertelt CPU – Z me 2 x 256, dus zet de waarde in mijn geval op 512.
  6. Klik op OK

Om L3-cache in Windows in te schakelen:

  1. Stap 1-2 zijn hetzelfde als L2 en komen daarom in hetzelfde venster terecht bij stap 3.
  2. Klik met de rechtermuisknop in het vrije gedeelte van het rechtervenster en klik op Nieuw > DWORD 32 Bit
  3. Hernoem de nieuwe sleutel naar "ThirdLevelDataCache" (zonder aanhalingstekens)
  4. Klik met de rechtermuisknop op deze nieuwe sleutel, hernoemd en klik op "Bewerken"
  5. Klik op de knop "Decimaal".
  6. Verander de waarde die uw processor wordt aangegeven met CPU - Z: in mijn geval ben ik 3 MBytes, dus ik zou 3 x 1024 moeten doen, wat betekent dat ik 3072 als waarde moet invoeren.
  7. Klik op OK.

Start uw computer opnieuw op. 1 Na het opnieuw opstarten kan uw computer iets langzamer zijn dan normaal, wat te wijten is aan het feit dat Windows deze nieuwe gegevens moet inschakelen, maar later zou uw computer sneller en krachtiger moeten zijn! Persoonlijk ben ik niet traag tijdens het opnieuw opstarten, ten eerste, maar ik heb een verbetering opgemerkt in de snelheid van programma's, vooral op multitasking-niveau, ondanks het feit dat WHO al een SSD op mijn computer heeft! Let op: Deze truc is niet overklokken, en daarom is er geen risico op oververhitting dat kan optreden bij overklokken.

Zoals u weet, is een computer geen tv en ontstaat de wens om deze te verbeteren al op de tweede dag na aankoop. Het eerste dat in je opkomt is het optimaliseren van Windows. Gelukkig is er voldoende materiaal over dit onderwerp, zowel op internet als op de pagina's van computerpublicaties. Maar is het de moeite waard om het systeem onmiddellijk op deze manier aan te vallen en is het altijd mogelijk om het te krijgen positief resultaat? Om de zenuwcellen, en in sommige gevallen het geld van gewone pc-gebruikers, te redden, stel ik voor om enkele problemen te begrijpen Windows-bediening meer gedetailleerd. Dus laten we beginnen.

Breng onder geen enkele omstandigheid wijzigingen aan in het register of systeembestanden Windows zonder dat u een reservekopie van de laatste hebt. Er zijn veel manieren om standaard een back-up van een systeem te maken met behulp van Windows, en met de hulp speciale programma's. Deze kwestie is herhaaldelijk behandeld op de pagina's van de computerpers en ik hoop dat elke lezer al de nodige ervaring heeft op het gebied van registerback-up. En voor degenen die hun eigen back-upprogramma willen maken zonder programmeertalen te studeren, raad ik aan het artikel te lezen, dat de creatie in detail beschrijft batchbestanden voor systeemback-up en -herstel. Dat zullen wij aannemen back-up Je hebt het al en we kunnen ons onderzoek voortzetten.

Cache-optimalisatie harde schijf.

Volgens veel experts op het gebied van Windows OS moet de cachegrootte vast zijn en volledig afhankelijk zijn van de hoeveelheid RAM die op de computer is geïnstalleerd. Ze zeggen dat Windows niet weet hoe het met de cache moet werken en er te veel RAM aan besteedt. Er zijn zelfs standaardschema's voorgesteld voor het bepalen van de cachegrootte op basis van de geheugengrootte. Dit alles is geschreven in de bestandssectie system.ini in de vorm MinFileCache = grootte in KB en MaxFileCache = grootte in KB. Deze aanpak zal inderdaad wat geheugen besparen, maar kan leiden tot algehele systeemvertraging, vooral op thuiscomputers, waar de hele dag tientallen zeer drukke taken kunnen worden uitgevoerd. verschillende programma's, zowel multimedia als kantoor, en elk van hen heeft dit nodig verschillende maten cache en RAM. Het is onwaarschijnlijk dat u veel plezier zult beleven aan het bewerken van system.ini en het opnieuw opstarten van de computer voordat u een nieuw programma start.

Universele maat schijfcache naar mijn mening is de mogelijkheid puur theoretisch, omdat er in sommige gevallen nog steeds een teveel aan cache en een gebrek aan RAM zal zijn, in andere gevallen - omgekeerd. En morgen brengt uw zoon een spel mee met niet-standaard gebruik van geheugenbronnen en moet system.ini opnieuw worden bewerkt? Moderne versies Windows kan programmacode rechtstreeks vanuit de cache uitvoeren, d.w.z. De cache is niet langer een tussenschakel tussen de harde schijf en het geheugen, maar is niets meer dan een onderdeel van het RAM-geheugen. Dus wat proberen we dan te beperken? Conclusie: Het beperken van de grootte van de cache van de harde schijf leidt in de meeste gevallen tot verminderde systeemprestaties. De enige uitzondering vormen computers die vergelijkbare taken uitvoeren, voornamelijk gerelateerd aan de overdracht van grote hoeveelheden gegevens. Dit is waar het optimaliseren van de cachegrootte echt zal helpen bereiken maximale prestaties systemen.

Virtueel geheugen.

Het merendeel van het advies over deze kwestie kwam aan het licht tijdens de zegevierende mars van Windows 95, toen het nieuwe GUI vereist extra en vrij duur RAM-geheugen. Waarom worden deze oude raden aan hun grijze baard naar nieuwe platforms gesleept? Moderne versies van Windows werken immers heel anders met virtueel geheugen. Wat wordt ons aangeboden te doen met het wisselbestand? Ja, alles is hetzelfde als voor oude vensters, namelijk: maak de grootte vast en gelijk aan 3-4 RAM-formaten. De noodzaak voor deze acties wordt meestal verklaard door het feit dat het besturingssysteem bijna een eeuwigheid besteedt aan het veranderen van de grootte van het wisselbestand, waardoor de gegevens onnodig gefragmenteerd raken en daardoor het systeem vertraagt.

Ik voorzie algemene verontwaardiging, maar ik durf nog steeds te beweren dat dergelijke verklaringen, als ze geen complete onzin zijn, dan toch al lang hun relevantie hebben verloren. Door Windows de mogelijkheid te ontnemen om zelfstandig de vereiste grootte van het wisselbestand te bepalen, loopt u het risico een bericht te ontvangen over de onmogelijkheid om te starten bepaalde programma's. Het risico neemt toe als Windows-multitasking wordt gebruikt. Er kan bijvoorbeeld gemakkelijk een situatie ontstaan ​​waarin u tegelijkertijd met Photoshop moet werken, tekstverwerker, HTML-editor en een andere animator. Waar is de garantie dat u op dit moment niet hoeft te openen? grafisch bestand enkele tientallen MB groot voor latere bewerking? Stel je voor dat het systeem op dit moment zou crashen omdat Windows de omvang niet zou kunnen vergroten virtueel geheugen, maar om een ​​of andere reden heb je de resultaten van je werk niet bewaard?! Het is niet meer grappig. Ik wil uw aandacht trekken lieve lezers op volgende feit. Windows wijzigt de grootte van het wisselbestand dynamisch en vooral wanneer de systeembronnen relatief vrij zijn en toegang tot de schijf geen enkel ongemak veroorzaakt.

Nadat de huidige taak is voltooid, blijft de grootte van het paginabestand gedurende een bepaalde periode ongewijzigd. Als je op dit moment nog een kop koffie gaat zetten, merk je niet eens enige actie van het besturingssysteem, maar als je liever doorwerkt, is het onwaarschijnlijk dat je deze 2,5 minuten werkeloos blijft zitten, maar start het volgende programma. Bovendien is de virtuele geheugenvereiste voor moderne programma's meerdere malen verminderd dankzij het gebruik van het zogenaamde directe leesprincipe (Linear Executable). Dergelijke programma's worden niet volledig in het geheugen geladen, maar wijzen hun code toe aan geheugenpagina's en laden indien nodig de benodigde bibliotheken. Dit zorgt voor de meest complete en optimaal gebruik zowel RAM als virtueel geheugen. Het is ook twijfelachtig om aan te nemen dat het aanpassen van de grootte van het wisselbestand onnodige gegevensfragmentatie zal voorkomen. Binnen het bestand zelf zullen de gegevens immers nog steeds gefragmenteerd zijn, misschien zelfs in grotere mate dan bij normaal gebruik. Wat betreft het verplaatsen van het wisselbestand naar het begin van de schijf of naar een afzonderlijk bestand fysieke schijf, dan hebben dergelijke methoden echt recht op leven. Maar het effect hiervan merk je nauwelijks.

Om dergelijke activiteiten uit te voeren, heb je speciale dure hulpprogramma's nodig, zoals het beroemde pakket van Mr. Norton, die onvermijdelijk hun programma's bij het opstarten zullen schrijven, waardoor het verbeterde virtuele geheugen wordt gecompenseerd met een afname van het fysieke geheugen. Om dit te voorkomen, moet u de hulpprogramma's zelf optimaliseren. Bovendien vereist het werken met hulpprogramma's op zijn minst een basiskennis van wat er gebeurt als ze worden gebruikt. Vooral geautomatiseerde functies zijn gevaarlijk. Ik heb meerdere keren systemen moeten herstellen nadat ik Norton Utilities had gebruikt. De reden is belachelijk eenvoudig: onjuiste regionale instellingen. Het punt is dat Norton-programma Disk Doctor, opgenomen in de hulpprogramma's, leest de landcode. En als de Russische versie van Windows op uw computer is geïnstalleerd en de regionale instellingen bijvoorbeeld de VS zijn, zal het programma alle Russischtalige bestands- en mapnamen als een fout beschouwen. Het resultaat is volgens mij duidelijk. En dit is nog maar een klein deel mogelijke problemen. Conclusie: Moderne versies van Windows hebben geen optimalisatie van het virtuele geheugen nodig. En als u toch besluit het wisselbestand naar het begin van de schijf of naar een aparte schijf te verplaatsen, vergeet dan niet de prijzen voor gelicentieerde hulpprogramma's + een extra harde schijf te bestuderen. Naar mijn mening zal een geheugenlijn veel minder kosten.

Internet en modem.

Geavanceerd Windows-gebruikers Het wordt aanbevolen om aan de registersleutel HKEY_LOCAL_MACHINE\ System\ CurrentControlSet\ Services\ Class\ NetTrans\ 0000 (misschien 0001, enz.) enkele parameters toe te voegen die een directe impact hebben op de prestaties van de modem. De belangrijkste parameter is MaxMTU. Laat me u eraan herinneren dat MTU (Maximum Transmition Unit) dat is maximale grootte een pakket gegevens dat in één fysiek frame over een netwerk kan worden verzonden. De meest optimale waarde is MaxMTU=576. Maar excuseer mij, waar is het beste voor? Om deze vraag te beantwoorden, stel ik voor een klein experiment uit te voeren. Laten we een gastinternetverbinding gebruiken die wordt aangeboden door een van de populairste providers in de hoofdstad: Svit Online. Om verbinding te maken met een externe computer, gebruiken we het nogal vergeten Hyper Terminal-programma, inbelnummer 490-0-490, login - svit, wachtwoord - online. En wat zien we na het invoeren van het wachtwoord?

Computer op afstand vertelt ons het toegewezen IP-adres en... MTU=1500! Is het u nu duidelijk waarom MaxMTU=576 optimaal is? Niet anders dan om de gegevensoverdracht te vertragen. Voor een optimale transmissie blijkt dat je moet uitgaan van de waarde van 1500. Ik zal niet in detail ingaan op de berekeningen van andere parameters, zoals MSS, TTL, aangezien ik al deze maatregelen als verre van ongevaarlijk beschouw, waarbij ik rekening houden met de kosten van de diensten van de provider en de betaling per minuut voor een vaste telefoon. Windows doet geweldig werk automatische detectie MTU zelf, zonder onze tussenkomst. Het is beter om uw aandacht te richten op het verbeteren van de kwaliteit van de communicatielijn, tenminste binnen uw eigen appartement. Vaker is de oorzaak van communicatieverslechtering verschillende soorten wendingen, slechte contacten en veel parallellen telefoontoestellen, niet hardware en zacht. Maar dit is een onderwerp voor een ander gesprek.

Windows Me leed ook het lot om geoptimaliseerd te worden. Wat ze hier niet bieden: verwijder PC Health, schakel Systeemherstel uit en verwijder het Mediaspeler 7 met Movie Maker, en vervang IE 5.5 door meer oude versie, en zelfs binnenkomen echte modus DOS. En dit alles alleen maar om vast te stellen nieuw besturingssysteem op een pc met Pentium 133 MHz en 32 MB RAM. Maar wat blijft er dan van over? O wat Windows-platform Ben ik aan het praten? Zelfs als het je lukt om een ​​Mercedes-motor in een Zaporozhets te plaatsen, wordt het immers geen Mesredes. Het resultaat is extra mislukkingen, ongemak op het werk en vaker - formaat C:. Dit is waar de gebruiker de verhalen over de legendarische problemen van Windows zal geloven. Om precies te zijn: hij zal het niet geloven, maar zal het controleren vanuit zijn eigen bittere ervaring.

De meeste programmeurs beschouwen een computersysteem als een processor die instructies uitvoert, en een geheugen dat instructies en gegevens voor de processor opslaat. In dit eenvoudige model wordt het geheugen weergegeven als een lineaire reeks bytes, en heeft de processor in constante tijd toegang tot elke locatie in het geheugen. Hoewel dit voor de meeste situaties een effectief model is, weerspiegelt het niet hoe moderne systemen werkelijk werken.

In werkelijkheid vormt zich het geheugensysteem hiërarchie van opslagapparaten met verschillende capaciteiten, kosten en toegangstijden. Processorregisters slaan de meest gebruikte gegevens op. Kleine, snelle caches die zich dicht bij de processor bevinden, dienen als bufferzones waarin een klein deel van de gegevens in het relatief trage RAM wordt opgeslagen. RAM dient als buffer voor langzaam lokale schijven. En lokale schijven dienen als buffer voor gegevens van externe machines die via een netwerk zijn verbonden.

De geheugenhiërarchie werkt omdat goed geschreven programma's vaker toegang hebben tot opslag op een bepaald niveau dan opslag op een lager niveau. Opslag op een lager niveau kan dus langzamer, groter en goedkoper zijn. Het eindresultaat is een grote hoeveelheid geheugen waarvan de opslagkosten helemaal onderaan de hiërarchie staan, maar gegevens aan het programma levert met de snelheid van snelle opslag helemaal bovenaan de hiërarchie.

Als programmeur moet u de geheugenhiërarchie begrijpen, omdat deze de prestaties van uw programma's enorm beïnvloedt. Als u begrijpt hoe het systeem gegevens omhoog en omlaag in de hiërarchie verplaatst, kunt u programma's schrijven die de gegevens hoger in de hiërarchie plaatsen, zodat de processor er sneller toegang toe heeft.

In dit artikel onderzoeken we hoe opslagapparaten in een hiërarchie zijn georganiseerd. We zullen ons vooral concentreren op cachegeheugen, dat dient als bufferzone tussen de processor en RAM. Het heeft de grootste impact op de programmaprestaties. We zullen een belangrijk concept introduceren plaats, zullen we leren programma's op locatie te analyseren, en ook technieken bestuderen die zullen helpen de locatie van uw programma's te vergroten.

Ik werd voor het schrijven van dit artikel geïnspireerd door het zesde hoofdstuk uit het boek Computersystemen: het perspectief van een programmeur. In een ander artikel in deze serie, Code Optimization: CPU, vechten we ook voor CPU-cycli.

Geheugen is ook belangrijk

Beschouw twee functies die de elementen van een matrix optellen. Ze zijn vrijwel identiek, alleen de eerste functie doorloopt de matrixelementen rij voor rij, en de tweede functie voor kolommen.

Int matrixsom1(int grootte, int M) ( int som = 0; for (int i = 0; i< size; i++) { for (int j = 0; j < size; j++) { sum += M[i][j]; // обходим построчно } } return sum; } int matrixsum2(int size, int M) { int sum = 0; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { sum += M[j][i]; // обходим по столбцам } } return sum; }

Beide functies voeren hetzelfde aantal processorinstructies uit. Maar met de auto mee Core i7 Haswell de eerste functie wordt uitgevoerd 25 keer sneller Voor grote matrixen. Dit voorbeeld laat dat goed zien geheugen is ook belangrijk. Als je de efficiëntie van programma's alleen meet aan de hand van het aantal uitgevoerde instructies, kun je zeer trage programma's schrijven.

Data heeft een belangrijke eigenschap die we noemen plaats. Wanneer we met gegevens werken, is het wenselijk dat ze in het geheugen dichtbij zijn. Kolomvormige doorgang van een matrix heeft een slechte lokaliteit omdat de matrix rij voor rij in het geheugen wordt opgeslagen. We zullen het hieronder over de plaats hebben.

Hiërarchie van het geheugen

Het moderne geheugensysteem vormt een hiërarchie van snelle kleine geheugentypen tot grote, langzame geheugentypen. We zeggen dat een bepaald niveau van hiërarchie caches of is cache voor gegevens die zich op een lager niveau bevinden. Dit betekent dat het kopieën bevat van gegevens met meer dan laag niveau. Wanneer de processor gegevens wil ontvangen, zoekt hij deze eerst op het snelste hoge niveau. En gaat naar lagere als hij ze niet kan vinden.

Bovenaan de hiërarchie bevinden zich de processorregisters. Om er toegang toe te krijgen zijn er 0 klokcycli nodig, maar er zijn er maar een paar. Vervolgens komen enkele kilobytes aan cache op het eerste niveau, waarvoor toegang ongeveer vier klokcycli duurt. Dan zijn er nog een paar honderd kilobytes langzamere L2-cache. Vervolgens enkele megabytes cache op het derde niveau. Het is veel langzamer, maar nog steeds sneller dan RAM. Het volgende is het relatief trage RAM-geheugen.

RAM kan worden gezien als een cache voor lokale schijf. Schijven zijn de werkpaarden van opslagapparaten. Ze zijn groot, langzaam en goedkoop. De computer laadt bestanden van schijf naar RAM wanneer hij eraan gaat werken. De kloof in toegangstijd tussen RAM en schijf is enorm. De schijf is tienduizenden keren langzamer dan RAM, en miljoenen keren langzamer dan de cache van het eerste niveau. Het is winstgevender om duizenden keren toegang te krijgen tot het RAM-geheugen dan om één keer toegang te krijgen tot de schijf. Deze kennis wordt ondersteund door datastructuren zoals B-bomen die proberen te plaatsen meer informatie in RAM, in een poging schijftoegang koste wat het kost te vermijden.

De lokale schijf zelf kan worden beschouwd als een cache voor gegevens die zich op externe servers bevinden. Wanneer u een website bezoekt, slaat uw browser afbeeldingen van de webpagina op uw schijf op, zodat u deze bij een volgend bezoek niet opnieuw hoeft te downloaden. Er zijn ook lagere geheugenhiërarchieën. Grote datacenters, zoals Google, sla grote hoeveelheden gegevens op tapemedia, die ergens in magazijnen worden opgeslagen en indien nodig handmatig of door een robot moeten worden bevestigd.

Het moderne systeem heeft ongeveer de volgende kenmerken:

Snel geheugen is erg duur en langzaam geheugen is erg goedkoop. Dit is het geweldige idee van systeemarchitecten om te combineren grote maten langzaam en goedkoop geheugen met kleine maten, snel en duur. Op deze manier kan het systeem op snelheid werken snel geheugen en hebben een lage prijs. Laten we uitzoeken hoe dit werkt.

Stel dat uw computer 8 GB RAM en een schijf van 1000 GB heeft. Maar houd er rekening mee dat u niet in één keer met alle gegevens op de schijf werkt. U laadt het besturingssysteem, opent een webbrowser, een teksteditor en een paar andere applicaties en werkt er enkele uren mee. Al deze toepassingen passen in het RAM-geheugen, zodat uw systeem geen toegang tot de schijf nodig heeft. Vervolgens sluit u natuurlijk de ene applicatie en opent u een andere, die van schijf in RAM moet worden geladen. Maar het duurt een paar seconden, waarna je enkele uren met deze applicatie werkt zonder toegang tot de schijf. Je merkt het niet echt langzame schijf, omdat je op een gegeven moment alleen met een kleine hoeveelheid gegevens werkt die in het RAM-geheugen zijn opgeslagen. Het heeft geen zin om veel geld uit te geven aan het installeren van 1024 GB RAM, waardoor de inhoud van de hele schijf zou kunnen worden geladen. Als je dit zou doen, zou je nauwelijks verschil in het werk merken, het zou ‘geld in de put’ zijn.

Hetzelfde geldt voor kleine processorcaches. Stel dat u berekeningen moet uitvoeren op een array die 1000 elementen van het type bevat int. Zo'n array beslaat 4 KB en past volledig in de eerste-niveaucache van 32 KB. Het systeem begrijpt dat u met een bepaald stuk RAM bent gaan werken. Het kopieert dit deel naar de cache en de processor voert snel acties uit op deze array, waarbij hij profiteert van de snelheid van de cache. Vervolgens wordt de gewijzigde array uit de cache terug naar het RAM gekopieerd. Het verhogen van de snelheid van RAM tot de snelheid van de cache zou geen merkbare prestatieverbetering opleveren, maar zou de kosten van het systeem honderden en duizenden keren verhogen. Maar dit alles is alleen waar als de programma's een goede locatie hebben.

Plaats

Plaats- het hoofdconcept van dit artikel. Over het algemeen worden programma's met een goede locatie sneller uitgevoerd dan programma's met een slechte locatie. Er zijn twee soorten locaties. Als we vaak dezelfde plek in het geheugen benaderen, is dat zo tijdelijke plaats. Wanneer we toegang krijgen tot gegevens en vervolgens toegang krijgen tot andere gegevens die zich in het geheugen bevinden naast de oorspronkelijke, is dit het geval ruimtelijke plaats.

Beschouw een programma dat de elementen van een array optelt:

Int som(int grootte, int A) ( int i, som = 0; for (i = 0; i< size; i++) sum += A[i]; return sum; }

In dit programma toegang tot variabelen som En i vindt plaats bij elke iteratie van de lus. Ze hebben een goede temporele lokaliteit en zullen zich in snelle processorregisters bevinden. Array-elementen A hebben een slechte temporele lokaliteit omdat we elk element slechts één keer benaderen. Maar ze hebben een goede ruimtelijke lokaliteit: nadat we één element hebben aangeraakt, raken we vervolgens de elementen ernaast aan. Alle gegevens in dit programma hebben een goede temporele lokaliteit of een goede ruimtelijke lokaliteit, dus we zeggen dat het programma in het algemeen een goede lokaliteit heeft.

Wanneer de processor gegevens uit het geheugen leest, kopieert hij deze naar de cache, terwijl andere gegevens uit de cache worden verwijderd. Welke gegevens hij wil verwijderen, is een complex onderwerp. Maar het resultaat is dat als bepaalde gegevens regelmatig worden benaderd, de kans groter is dat deze in de cache blijven staan. Dit is het voordeel van tijdelijke lokaliteit. Het is beter dat een programma met minder variabelen werkt en er vaker toegang toe heeft.

Gegevens worden tussen hiërarchieniveaus verplaatst in blokken van een bepaalde grootte. Een verwerker bijvoorbeeld Core i7 Haswell verplaatst gegevens tussen de caches in blokken van 64 bytes. Laten we eens overwegen concreet voorbeeld. We voeren het programma uit op een machine met de bovengenoemde processor. We hebben een array v, met elementen van 8 bytes van het type lang. En we doorkruisen de elementen van deze array opeenvolgend in een lus. Als we lezen v, het staat niet in de cache, de processor leest het van RAM naar de cache in een blok van 64 bytes. Dat wil zeggen dat elementen naar de cache worden verzonden vv. Vervolgens herhalen we de elementen v, v, ..., v. Ze bevinden zich allemaal in de cache en we zullen er snel toegang toe krijgen. Vervolgens lezen we het element v, die niet in de cache staat. De processor kopieert de elementen vv cachen. We doorkruisen deze elementen snel, maar vinden het element niet in de cache v. En zo verder.

Dus als u enkele bytes uit het geheugen leest en vervolgens de bytes ernaast leest, bevinden deze zich hoogstwaarschijnlijk in de cache. Dit is het voordeel van ruimtelijke lokaliteit. In elke fase van de berekening moeten we ernaar streven om te werken met gegevens die zich dichtbij in het geheugen bevinden.

Het is raadzaam om de array opeenvolgend te doorlopen en de elementen één voor één te lezen. Als u de elementen van een matrix moet doorlopen, is het beter om de matrix rij voor rij te doorlopen in plaats van kolom voor kolom. Dit levert een goede ruimtelijke locatie op. Nu kunt u begrijpen waarom de functie matrixsom1 werkte langzamer dan de functie matrixsom2. Een tweedimensionale array wordt rij voor rij in het geheugen geplaatst: de eerste rij bevindt zich als eerste, onmiddellijk gevolgd door de tweede, enzovoort. De eerste functie las de elementen van de matrix rij voor rij en bewoog zich opeenvolgend door het geheugen, alsof hij één grote eendimensionale array doorkruiste. Deze functie leest in feite gegevens uit de cache. De tweede functie ging van regel naar regel en las element voor element. Ze leek vanuit haar geheugen van links naar rechts te springen, keerde toen terug naar het begin en begon opnieuw van links naar rechts te springen. Aan het einde van elke iteratie vulde het de cache met de laatste regels, zodat aan het begin van de volgende iteratie de eerste regels niet in de cache werden gevonden. Deze functie leest in principe gegevens uit RAM.

Cachevriendelijke code

Als programmeurs moet je proberen code te schrijven waarvan wordt gezegd dat cachevriendelijk (cache-vriendelijk). In de regel wordt het grootste deel van de berekeningen op slechts enkele plaatsen in het programma uitgevoerd. Meestal zijn dit verschillende sleutelfuncties en lussen. Als er geneste lussen zijn, moet de aandacht worden gericht op de binnenste lus, omdat de code daar het vaakst wordt uitgevoerd. Deze plaatsen in het programma moeten worden geoptimaliseerd, in een poging hun locatie te verbeteren.

Matrixberekeningen zijn erg belangrijk bij signaalanalyse en wetenschappelijke computertoepassingen. Als programmeurs worden geconfronteerd met de taak om een ​​matrixvermenigvuldigingsfunctie te schrijven, dan zal 99,9% van hen deze ongeveer zo schrijven:

Leegte matrixmult1(int size, double A, double B, double C) ( dubbele som; for (int i = 0; i< size; i++) for (int j = 0; j < size; j++) { sum = 0.0; for (int k = 0; k < size; k++) sum += A[i][k]*B[k][j]; C[i][j] = sum; } }

Deze code herhaalt letterlijk de wiskundige definitie van matrixvermenigvuldiging. We doorlopen alle elementen van de uiteindelijke matrix rij voor rij en berekenen ze stuk voor stuk. Er zit één inefficiëntie in de code: deze uitdrukking B[k][j] in de binnenste lus. We gaan de matrix rond B door kolommen. Het lijkt erop dat er niets aan te doen is en dat je er mee in het reine zult moeten komen. Maar er is een uitweg. U kunt dezelfde berekening anders herschrijven:

Leegte matrixmult2(int size, double A, double B, double C) ( double r; for (int i = 0; i< size; i++) for (int k = 0; k < size; k++) { r = A[i][k]; for (int j = 0; j < size; j++) C[i][j] += r*B[k][j]; } }

Nu ziet de functie er heel vreemd uit. Maar zij doet precies hetzelfde. Alleen berekenen we niet elk element van de uiteindelijke matrix tegelijk; het lijkt erop dat we de elementen bij elke iteratie gedeeltelijk berekenen. Maar de belangrijkste eigenschap van deze code is dat we in de binnenste lus beide matrices rij voor rij doorkruisen. Met de auto mee Core i7 Haswell tweede functie werkt 12 keer sneller voor grote matrices. Je moet een heel slimme programmeur zijn om je code op deze manier te organiseren.

Blokkeren

Er is een techniek genaamd blokkeren. Stel dat u een berekening moet uitvoeren groot volume gegevens die niet allemaal in de cache passen hoog niveau. Deze gegevens verdeel je in blokken kleiner formaat, die elk in de cache worden geplaatst. Op deze blokken voer je afzonderlijk berekeningen uit en combineer je vervolgens het resultaat.

We kunnen dit aantonen met een voorbeeld. Stel dat u een gerichte grafiek heeft, weergegeven als een aangrenzende matrix. Dit is een vierkante matrix van nullen en enen, zodat als een element van de matrix met index (i, j) gelijk is aan één, er een rand is van grafiekpunt i naar hoekpunt j. U wilt van deze gerichte grafiek een ongerichte grafiek maken. Dat wil zeggen, als er een gezicht (i, j) is, dan zou het tegenovergestelde gezicht (j, i) moeten verschijnen. Houd er rekening mee dat als u de matrix visueel weergeeft, de elementen (i, j) en (j, i) symmetrisch zijn ten opzichte van de diagonaal. Deze transformatie is eenvoudig in code te implementeren:

Void convert1(int size, int G) ( for (int i = 0; i< size; i++) for (int j = 0; j < size; j++) G[i][j] = G[i][j] | G[j][i]; }

Blokkeren gebeurt van nature. Stel je voor dat er een grote voor je ligt vierkante matrix. Snijd deze matrix nu met horizontale en verticale lijnen om deze in bijvoorbeeld 16 gelijke blokken te verdelen (vier rijen en vier kolommen). Kies twee willekeurige symmetrische blokken. Merk op dat alle elementen in één blok symmetrische elementen in een ander blok hebben. Dit suggereert dat dezelfde bewerking achtereenvolgens op elk blok kan worden uitgevoerd. In dit geval zullen we in elke fase met slechts twee blokken werken. Als de blokken klein genoeg worden gemaakt, passen ze in de cache op hoog niveau. Laten we dit idee in code uitdrukken:

Void convert2(int size, int G) ( int block_size = size / 12; // opgesplitst in 12*12 blokken // stel je voor dat het is verdeeld zonder een rest voor (int ii = 0; ii< size; ii += block_size) { for (int jj = 0; jj < size; jj += block_size) { int i_start = ii; // индекс i для блока принимает значения [j] = G[i][j] | G[j][i]; } } }

Opgemerkt moet worden dat het blokkeren de prestaties op systemen met krachtige processors, die goed prefetchen. Op systemen die niet vooraf ophalen, kan blokkeren de prestaties aanzienlijk verbeteren.

Op een machine met een processor Core i7 Haswell de tweede functie wordt niet sneller uitgevoerd. Op een machine met een eenvoudiger processor Pentium2117U tweede functie wordt uitgevoerd 2 keer sneller. Op machines die niet vooraf ophalen, zouden de prestaties nog verder verbeteren.

Welke algoritmen zijn sneller?

Iedereen weet uit cursussen over algoritmen wat je moet kiezen Goed algoritmen met de minste complexiteit en vermijden slecht algoritmen met een hoge complexiteit. Maar deze moeilijkheden evalueren de uitvoering van het algoritme op een theoretische machine die door ons denken is gecreëerd. Op echte machines kan een theoretisch slecht algoritme sneller worden uitgevoerd dan een theoretisch goed algoritme. Houd er rekening mee dat het verkrijgen van gegevens uit het RAM 200 klokcycli duurt, en uit de cache van het eerste niveau 4 klokcycli - dit is 50 keer sneller. Als een goed algoritme vaak in het geheugen terechtkomt en een slecht algoritme zijn gegevens in de cache plaatst, kan het goede algoritme langzamer werken dan het slechte. Bovendien kan een goed algoritme slechter presteren op een processor dan een slecht algoritme. Een goed algoritme introduceert bijvoorbeeld gegevensafhankelijkheid en kan de pijplijn niet laden, maar een slecht algoritme heeft dit probleem niet en stuurt bij elke klokcyclus een nieuwe instructie naar de pijplijn. Met andere woorden: de complexiteit van het algoritme zegt niet alles. Hoe het algoritme wordt uitgevoerd op een specifieke machine met specifieke gegevens is van belang.

Laten we ons voorstellen dat u een wachtrij met gehele getallen moet implementeren, en dat nieuwe elementen aan elke positie in de wachtrij kunnen worden toegevoegd. U kiest tussen twee implementaties: array en linked list. Om een ​​element aan het midden van een array toe te voegen, moet je de helft van de array naar rechts verschuiven, wat lineaire tijd kost. Om een ​​element aan het midden van de lijst toe te voegen, moet je langs de lijst naar het midden lopen, wat ook lineaire tijd kost. Je denkt dat, aangezien ze dezelfde problemen hebben, het beter is om een ​​lijst te kiezen. Bovendien heeft de lijst één goede eigenschap. De lijst kan onbeperkt groeien, maar de array zal moeten worden uitgebreid als deze vol is. Laten we ons voorstellen dat de lengte van de wachtrij 1000 elementen is en dat we een element in het midden van de wachtrij moeten invoegen. De lijstelementen zijn willekeurig verspreid over het geheugen en het ophalen van gegevens uit het geheugen duurt 200 klokcycli. Om 500 elementen te verplaatsen, hebben we daarom 500*200=100"000 klokcycli nodig. De array bevindt zich sequentieel in het geheugen, waardoor we kunnen profiteren van de snelheid van de cache van het eerste niveau met een toegangstijd van 4 klokcycli. Met behulp van verschillende optimalisaties kunnen we elementen verplaatsen door 1-4 cycli per element te besteden. We verschuiven de helft van de array in maximaal 500*4=2000 klokcycli. Dit is sneller. 50 keer.

Als in het vorige voorbeeld alle toevoegingen bovenaan de wachtrij zouden staan, zou een implementatie van een gekoppelde lijst efficiënter zijn. Als sommige toevoegingen zich ergens in het midden van de wachtrij zouden bevinden, zou de array-implementatie kunnen worden beste keuze. We zouden cycli besteden aan sommige operaties en cycli besparen op andere operaties. En uiteindelijk zouden ze wel eens kunnen winnen. Er moet rekening worden gehouden met alle aspecten van elke specifieke situatie.

Conclusie

Het geheugensysteem is georganiseerd in een hiërarchie van opslagapparaten, met kleine, snelle apparaten bovenaan de hiërarchie en grote, langzame apparaten onderaan. Programma's met een goede locatie werken met gegevens uit processorcaches. Programma's met een slechte locatie werken met gegevens uit relatief trage RAM.

Programmeurs die de aard van de geheugenhiërarchie begrijpen, kunnen hun programma's zo structureren dat gegevens zo hoog mogelijk in de hiërarchie worden geplaatst, zodat de processor er sneller toegang toe heeft.

  • Richt uw aandacht op de binnenste lussen. Dat is waar het gebeurt grootste volume berekeningen en geheugentoegang.
  • Probeer de ruimtelijke lokaliteit te maximaliseren door objecten opeenvolgend uit het geheugen te lezen, in de volgorde waarin ze zich daarin bevinden.
  • Probeer de temporele lokaliteit te maximaliseren door data-objecten zo vaak mogelijk te gebruiken nadat ze uit het geheugen zijn gelezen.

Het delen van wat we hebben met onze buren is heel typerend voor ons, Gods schepselen, en wordt als een deugd beschouwd, en heeft, zoals het zegt, in het algemeen een positief effect op karma. In de wereld gecreëerd door microprocessorarchitecten leidt dit gedrag echter niet altijd tot goede resultaten, vooral als het gaat om het delen van geheugen tussen threads.

We hebben allemaal "een beetje" gelezen over geheugenoptimalisatie, en we weten wat handig is als de cache warm blijft, wat betekent dat gegevens die vaak door threads worden benaderd, compact moeten zijn en zich op de dichtstbijzijnde locatie moeten bevinden. processorkern cache. Dat is waar, maar als het gaat om het delen van toegang, worden threads de ergste vijanden [van de prestaties], en de cache is niet alleen heet, hij staat al "brandend van het hellevuur" - zo is de strijd die zich eromheen afspeelt.

Hieronder zullen we een eenvoudig maar illustratief geval van prestatieproblemen bekijken. programma's met meerdere threads en dan geef ik je er een paar algemene aanbevelingen, hoe je het probleem van verlies van rekenefficiëntie als gevolg van het delen van cache tussen threads kunt voorkomen.

Laten we een geval bekijken dat goed wordt beschreven in de Intel64 en IA-32 Architectures Optimization Manual, maar dat programmeurs vaak vergeten als ze met arrays van structuren in multi-threaded modus werken. Ze bieden stromen toegang (met wijziging) tot gegevens van structuren die zich zeer dicht bij elkaar bevinden, namelijk in een blok. gelijk aan lengteéén cacheregel (64 bytes). Wij noemen het Сache lijn delen. Er zijn twee typen cacheregelpartitionering: echt delen En vals delen.
Echt delen(echt delen) is wanneer threads toegang hebben tot hetzelfde geheugenobject, zoals een gedeelde variabele of synchronisatieprimitief. Vals delen(van de boze) - dit is toegang tot verschillende gegevens, maar om de een of andere reden komt het in dezelfde cacheregel van de processor terecht. Laten we meteen opmerken dat beide gevallen de prestaties schaden vanwege de noodzaak voor hardwaresynchronisatie van de processorcache. Als het eerste geval echter vaak onvermijdelijk is, kan en moet het tweede worden uitgesloten.

Laten we met een voorbeeld uitleggen waarom de productiviteit eronder lijdt. Laten we zeggen dat we een reeks gegevensstructuren in de wachtrij op een multi-threaded manier verwerken. Actieve threads worden één voor één verwijderd volgende structuur uit de wachtrij en verwerk het op een of andere manier, waarbij de gegevens worden gewijzigd. Wat kan er gebeuren op hardwareniveau, als de omvang van deze structuur bijvoorbeeld klein is en niet groter is dan enkele tientallen bytes?


Voorwaarden voor het optreden van het probleem:
Twee of meer threads schrijven naar dezelfde cacheregel;
Eén thread schrijft, de rest leest vanaf de cacheregel;
Eén thread schrijft, maar de HW-prefetcher werkt in de andere kernen.

Het kan blijken dat de variabelen in de velden van verschillende structuren zich zo in het geheugen bevinden dat ze, wanneer ze in de L1-cache van de processor worden gelezen, zich in dezelfde cacheregel bevinden, zoals in de figuur. Bovendien, als een van de threads een veld van zijn structuur wijzigt, wordt de gehele cachelijn, in overeenstemming met het cachecoherentieprotocol, ongeldig verklaard voor de resterende processorkernen. Een andere thread zal zijn structuur niet langer kunnen gebruiken, ondanks het feit dat deze zich al in de L1-cache van zijn kernel bevindt. Bij oudere processors van het P4-type zou een dergelijke situatie een lange synchronisatie met het hoofdgeheugen vereisen, dat wil zeggen dat de gewijzigde gegevens naar het hoofdgeheugen zouden worden gestuurd en vervolgens in de L1-cache van een andere kern zouden worden gelezen. In de huidige generatie processors (codenaam Zandige brug) wordt het synchronisatiemechanisme gebruikt gedeelde cache derde niveau (of LLC - Last Level Cache), dat inclusief is voor het cachegeheugensubsysteem en waarin alle gegevens zich in zowel L2 als L1 van alle processorkernen bevinden. Synchronisatie vindt dus niet plaats met het hoofdgeheugen, maar met de LLC, die deel uitmaakt van de protocolimplementatie van het cache-coherentiemechanisme, die veel sneller is. Maar het gebeurt nog steeds, en het kost tijd, ook al wordt het gemeten in slechts enkele tientallen processorcycli. En als de gegevens in de cacheregel zijn verdeeld over threads die in verschillende threads worden uitgevoerd fysieke verwerkers? Dan zul je moeten synchroniseren tussen de LLC van verschillende chips, en dit zal veel langer duren: honderden klokcycli. Laten we ons nu voorstellen dat het programma alleen bezig is met het in een lus verwerken van een stroom gegevens die van een bepaalde bron wordt ontvangen. Door bij elke iteratie van de lus honderden klokcycli te verliezen, lopen we het risico onze productiviteit aanzienlijk te verminderen.

Laten we eens kijken naar het volgende voorbeeld, dat vereenvoudigd is om het gemakkelijker te maken de oorzaak van het probleem te begrijpen. Twijfel er niet aan echte toepassingen dezelfde gevallen komen heel vaak voor, en in tegenstelling tot het verfijnde voorbeeld is zelfs het opsporen van het bestaan ​​van een probleem niet zo eenvoudig. Hieronder laten we u zien hoe u deze situaties snel kunt vinden met behulp van de Performance Profiler.

De threadfunctie loopt door twee float-arrays a[i] en b[i], vermenigvuldigt hun waarden met de array-index en voegt ze toe aan lokale threadvariabelen localSum. Om het effect te vergroten, wordt deze bewerking meerdere keren (ITERATIES) uitgevoerd.

< ITERATIONS; j++){ for (i = tid; i < MAXSIZE; i+= NUM_PROCS){ a[i] = i + a[i] * b[i]; localSum += a[i];}} }

Het probleem is dat de gekozen methode om gegevens tussen threads te verdelen het interleaven van lusindexen is. Dat wil zeggen, als er twee threads actief zijn, heeft de eerste toegang tot de elementen van de arrays a en b, de tweede heeft toegang tot de elementen a en b, de eerste heeft toegang tot a en b, de tweede heeft toegang tot a en b, enzovoort. op. In dit geval worden de elementen van de array a[i] gewijzigd door threads. Het is niet moeilijk om te zien dat 16 array-elementen in één cacheregel zullen vallen, en dat threads tegelijkertijd toegang zullen krijgen tot aangrenzende elementen, waardoor het cachesynchronisatiemechanisme van de processor gek wordt.

Het meest onaangename is dat we het bestaan ​​van dit probleem niet eens zullen merken als het programma draait. Het werkt alleen langzamer dan het kan, dat is alles. Ik heb al beschreven hoe je de effectiviteit van een programma kunt evalueren met behulp van de VTune Amplifier XE profiler in een van de berichten op Habré. Een profiel gebruiken Algemene verkenning, die ik daar noemde, kunt u het beschreven probleem zien, dat door de tool zal worden “gemarkeerd” in de profileringsresultaten in de kolom Betwiste toegang. Deze statistiek meet nauwkeurig de verhouding van de cycli die zijn besteed aan het synchroniseren van processorcaches wanneer deze door threads worden gewijzigd.

Als iemand geïnteresseerd is in wat er achter deze statistiek zit, verzamelt de tool tijdens complexe profilering tellergegevens tussen andere hardwaretellers:
MEM_LOAD_UOPS_LLC_HIT_RETIRED.XSNP_HITM_PS– Exacte teller (PS) van voltooide (RETIRED) bewerking (OUPS) laden (LOAD) van gegevens (MEM), die in LLC zijn beland (HIT) en gewijzigd (M). Een “precieze” teller betekent dat de gegevens die door een dergelijke teller in het monster worden verzameld, relatief zijn ten opzichte van de instructieaanwijzer (IP) die volgt op de instructie die precies de belasting was die ervoor zorgde dat de caches synchroniseerden. Door statistieken over deze metriek te typen, kunnen we met een zekere nauwkeurigheid het adres van de instructie aangeven, en dienovereenkomstig de regel broncode waar de lezing plaatsvond. VTune Amplifier XE kan laten zien welke threads deze gegevens lezen, en dan moeten we zelf uitzoeken hoe multi-threaded datatoegang wordt geïmplementeerd en hoe we de situatie kunnen corrigeren.

Wat betreft onze eenvoudig voorbeeld de situatie is heel eenvoudig op te lossen. U hoeft de gegevens alleen maar in blokken te verdelen, en het aantal blokken is gelijk aan het aantal threads. Je zou kunnen stellen dat als de arrays groot genoeg zijn, de blokken eenvoudigweg niet in de cache passen, en dat gegevens die voor elke thread uit het geheugen worden geladen, elkaar uit de cache zullen duwen. Dit zal het geval zijn als alle blokgegevens constant worden gebruikt, en niet slechts één keer. Bij het vermenigvuldigen van matrices gaan we bijvoorbeeld door de elementen tweedimensionale array eerst per rij, daarna per kolom. En als beide matrices niet in de cache passen (op welk niveau dan ook), worden ze verwijderd en moet bij herhaalde toegang tot elementen opnieuw worden geladen vanaf het volgende niveau, wat de prestaties negatief beïnvloedt. In het algemene geval met matrices wordt een aangepaste blok-voor-blok matrixvermenigvuldiging gebruikt, waarbij de matrices worden verdeeld in blokken, die uiteraard in een bepaald cachegeheugen worden geplaatst, wat de prestaties van het algoritme aanzienlijk verhoogt.

Int werk(void *pArg) ( int j = 0, i = 0; int tid = (int) pArg; for (j = 0; j< ITERATIONS; j++){ chunks = MAXSIZE / NUM_PROCS; for (i = tid * chunks; i < (tid + 1) * chunks; i++){ a[i] = i + a[i] * b[i]; localSum += a[i];}} }

Vals delen

Geen vals delen

Vergelijking van threadtoegang tot array-elementen in het geval van False sharing en in de gecorrigeerde code

In onze eenvoudig geval de gegevens worden slechts één keer gebruikt, en zelfs als ze uit de cache worden verwijderd, hebben we ze niet langer nodig. En de hardware prefetcher, een mechanisme voor het uitwisselen van gegevens uit het hoofdgeheugen geïmplementeerd in de processor, zal ervoor zorgen dat de gegevens van beide arrays a[i] en b[i], die zich ver van elkaar in de adresruimte bevinden, in de cache staan. op tijd. Het werkt prima als de array-elementen opeenvolgend worden benaderd.

Concluderend kunnen we enkele algemene aanbevelingen doen over hoe het probleem van verlies aan rekenefficiëntie als gevolg van het delen van cache tussen threads kan worden vermeden. Uit de naam van het probleem kun je begrijpen dat je moet vermijden dat je codeert waar threads heel vaak toegang hebben tot gedeelde gegevens. Als dit waar is, is het delen van de mutex via threads, dan is er misschien sprake van een probleem van overmatige synchronisatie, en moet de aanpak voor het delen van de bron die door deze mutex wordt beschermd, worden heroverwogen. Probeer in het algemeen globale en statische variabelen te vermijden die toegankelijk moeten zijn via threads. Gebruik lokale threadvariabelen.

Als u met datastructuren in multi-threaded modus werkt, let dan op hun grootte. Gebruik opvulling om de structuurgrootte te vergroten tot 64 bytes:
struct data_packet ( int adres; int data; int attribuut; int opvulling; )
Wijs geheugen toe voor structuren op uitgelijnde adressen:
__declspec(align(64)) struct data_packet sendpack
Gebruik arraystructuren in plaats van arraystructuren:
data_pakket verzendpakket;
in plaats van
struct data_packet ( int adres; int data; int attribuut; )
Zoals u kunt zien, zullen in het laatste geval threads die een van de velden wijzigen het cachesynchronisatiemechanisme activeren.

Voor objecten die in dynamisch geheugen zijn toegewezen met behulp van malloc of new, maakt u lokale geheugenpools voor threads, of gebruikt u parallelle bibliotheken die dit zelf kunnen doen. De TBB-bibliotheek bevat bijvoorbeeld schaalbare en nivellerende allocators, die handig zijn voor de schaalbaarheid van programma's met meerdere threads.

Nog een laatste advies: haast u niet om een ​​probleem op te lossen als dit geen grote invloed heeft op de algehele prestaties van de applicatie. Evalueer altijd de potentiële winst die u haalt uit de kosten voor het optimaliseren van uw code. Gebruik profileringstools om deze winst te evalueren.

P.S. Probeer mijn voorbeeld en vertel me met welk percentage de testprestaties op uw platform zijn toegenomen.

Tags: tags toevoegen