Zoeken…


Invoering

Dit onderwerp introduceert enkele van de onderliggende kernmechanismen van parallel computing die nodig zijn om OpenCL volledig te begrijpen en te gebruiken.

Threads en uitvoering

De sleutel van parallellisme is om meerdere threads te gebruiken om een probleem op te lossen (duh.) Maar er zijn enkele verschillen met klassieke multithreaded programmeren in hoe threads zijn georganiseerd.

Laten we eerst praten over uw typische GPU, omwille van de eenvoud zal ik me concentreren op

Een GPU heeft veel verwerkingskernen, waardoor het ideaal is om meerdere threads parallel uit te voeren. Die kernen zijn georganiseerd in Streaming Processors (SM, NVidia-term), waarvan een GPU een bepaald nummer heeft.

Alle threads die binnen een SM lopen, worden een 'thread block' genoemd. Er kunnen meer threads op een SM zijn dan cores. Het aantal kernen definieert de zogenaamde 'Warp-grootte' (NVidia-term). Draden in een draadblok worden ingepland in zogenaamde 'warps'.

Een snel voorbeeld om op te volgen: een typische NVidia SM heeft 32 verwerkingskernen, dus de scheringgrootte is 32. Als mijn threadblok nu 128 threads moet uitvoeren, worden deze in 4 schering (4 schering * 32 scheringgrootte = 128) gepland draden).

De kettingmaat is nogal belangrijk bij het later kiezen van het aantal draden.

Alle threads binnen een enkele warp delen een enkele instructieteller. Dat betekent dat die 32 threads echt gesynchroniseerd zijn, omdat elke thread elke opdracht tegelijkertijd uitvoert. Hier ligt een valkuil van de prestaties: dit geldt ook voor vertakkingen in uw kernel!

Voorbeeld: ik heb een kernel met een if-instructie en twee takken. 16 van mijn draden in een ketting zullen tak één uitvoeren, de andere 16 tak twee. Tot de if-opdracht zijn alle threads in de warp gesynchroniseerd. Nu kiest de helft een andere tak. Wat er gebeurt, is dat de andere helft slapend blijft liggen totdat de verkeerde instructie is voltooid op de eerste 16 threads. Dan zullen die draden inactief zijn totdat de andere 16 draden hun tak afmaken.

Zoals u kunt zien, kunnen slechte vertakkingsgewoonten uw parallelle code ernstig vertragen, omdat beide instructies in het ergste geval worden uitgevoerd. Als alle threads in een warp besluiten dat ze slechts één van de statements nodig hebben, wordt de andere volledig overgeslagen en treedt er geen vertraging op.

Discussies synchroniseren is ook niet eenvoudig. U kunt alleen threads synchroniseren binnen een enkele SM. Alles buiten de SM is niet te synchroniseren vanuit de kernel. Je zult afzonderlijke kernels moeten schrijven en ze na elkaar moeten opstarten.

GPU-geheugen

De GPU biedt zes verschillende geheugengebieden. Ze verschillen in hun latentie, grootte en toegankelijkheid van verschillende threads.

  • Wereldwijd geheugen: het grootste beschikbare geheugen en een van de weinige om gegevens uit te wisselen met de host. Dit geheugen heeft de hoogste latentie en is beschikbaar voor alle threads.
  • Constant geheugen: een alleen-lezen deel van het globale geheugen, dat alleen door andere threads kan worden gelezen. Het voordeel is de lagere latentie in vergelijking met het wereldwijde geheugen
  • Texture Memory: ook onderdeel van constant geheugen, speciaal ontworpen voor texturen
  • Gedeeld geheugen: dit geheugengebied bevindt zich dicht bij de SM en is alleen toegankelijk via een enkel threadblok. Het biedt een veel lagere latentie dan het wereldwijde geheugen en iets minder latentie dan het constante geheugen.
  • Registers: alleen toegankelijk via een enkele thread en de snelste herinnering van allemaal. Maar als de compiler detecteert dat er niet genoeg registers zijn voor de kernelbehoeften, zal hij variabelen uitbesteden aan het lokale geheugen.
  • Lokaal geheugen: een alleen toegankelijk deel van het geheugen in het wereldwijde geheugengebied. Gebruikt als back-up voor registers, indien mogelijk te vermijden.

Geheugentoegang

Het typische scenario voor uw geheugengebruik is om de brongegevens en de verwerkte gegevens in het wereldwijde geheugen op te slaan. Wanneer een threadblock start, worden eerst alle relevante delen in het gedeelde geheugen gekopieerd voordat hun delen in de registers worden opgenomen.

De latentie voor geheugentoegang is ook afhankelijk van uw geheugenstrategie. Als u blind toegang tot gegevens krijgt, krijgt u de slechtst mogelijke prestaties.

De verschillende herinneringen zijn georganiseerd in zogenaamde 'banken'. Elk geheugenverzoek voor een bank kan in een enkele klokcyclus worden afgehandeld. Het aantal banken in het gedeelde geheugen is gelijk aan de scheringgrootte. De geheugensnelheid kan worden verhoogd door conflicterende banktoegang binnen een enkele warp te vermijden.

Om het gedeelde geheugen van of naar het wereldwijde geheugen te kopiëren, is de snelste manier om uw geheugenoproepen te 'uitlijnen'. Dit betekent dat de eerste thread in een ketting toegang moet hebben tot het eerste element in de bank van zowel het gedeelde als het globale geheugen. De tweede draad het tweede element enzovoort. Deze oproep wordt geoptimaliseerd in een enkele geheugenoverdrachtinstructie die de hele bank in één keer naar het doelgeheugen kopieert.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow