Zoeken…


Invoering

Voordat u OpenCL gebruikt, moet u hun code instellen om deze te gebruiken. Dit onderwerp richt zich op het openen van opencl in uw project en het uitvoeren van een basiskernel. De voorbeelden zijn gebaseerd op de C # wrapper OpenCL.NET maar omdat de wrapper geen abstractie toevoegt aan OpenCL zal de code waarschijnlijk ook met zeer weinig wijzigingen op C / C ++ worden uitgevoerd.

Oproepen in C # kunnen er als volgt uitzien: 'Cl.GetPlatformIDs'. Voor de C-Style OpenCL Api zou je 'clGetPlatformIDs' noemen en voor de C ++ style one 'cl :: GetPlatformIDs'

Opmerkingen

  • NVidia, AMD en Intel hebben enigszins verschillende implementaties van OpenCL, maar de bekende verschillen zijn (voor mijn ervaring) beperkt tot haakjesvereisten en impliciete casts. Soms crasht NVidia je kernel terwijl ze probeert de juiste overbelasting voor een methode te achterhalen. In dit geval helpt het om een expliciete cast aan te bieden om de GPU te helpen. Het probleem werd waargenomen voor runtime-gecompileerde kernels.

  • Om meer informatie te krijgen over de gebruikte aanroepen in dit onderwerp, is het voldoende om 'OpenCL' gevolgd door de functienaam te googelen. De Khronos-groep heeft een complete documentatie over alle parameters en datatypes beschikbaar op hun website.

Het doelapparaat initialiseren

OpenCL-kernels kunnen worden uitgevoerd op de GPU of de CPU. Dit maakt fallback-oplossingen mogelijk, waarbij de klant mogelijk een zeer verouderd systeem heeft. De programmeur kan er ook voor kiezen om zijn functionaliteit te beperken tot de CPU of GPU.

Om aan de slag te gaan met OpenCL, hebt u een 'context' en een 'apparaat' nodig. Beide zijn structuren gedefinieerd door de OpenCL API (ook bekend als cl :: Context of clContext & ~ Device) en definiëren de gebruikte doelprocessor.

Om uw apparaat en context te krijgen, moet u een lijst met beschikbare platforms opvragen, die elk meerdere apparaten kunnen hosten. Een platform vertegenwoordigt uw fysieke GPU en CPU, terwijl een apparaat de aanwezige rekeneenheden verder kan onderscheiden. Voor GPU's hebben de meeste platforms slechts één apparaat. Maar een CPU kan naast zijn CPU-mogelijkheden een extra geïntegreerde GPU bieden.

De context beheert geheugen, opdrachtwachtrijen, de verschillende kernels en programma's. Een context kan worden beperkt tot één apparaat maar ook verwijzen naar meerdere apparaten.

Een korte API-opmerking voordat we beginnen met coderen: bijna elke aanroep van OpenCL geeft u een foutwaarde, hetzij als retourwaarde of via een ref-waarde (aanwijzer in C). Laten we nu beginnen.

ErrorCode err;
var platforms = Cl.GetPlatformIDs(out err);
if(!CheckError(err, "Cl.GetPlatformIDs")) return;
foreach (var platform in platforms) {
    foreach (var device in Cl.GetDeviceIDs(platform, DeviceType.Gpu, out err)) {
        if(!CheckError(err, "Cl.GetDeviceIDs")) continue;
        [...]
    }
}

Dit codefragment doorzoekt alle beschikbare GPU-apparaten op het systeem. Je kunt ze nu toevoegen aan een lijst of je context direct beginnen met de eerste match. De functie 'CheckError (...)' is een eenvoudig hulpprogramma dat controleert of de foutcode de succeswaarde of een andere heeft en u wat logboekregistratie kan bieden. Het wordt aanbevolen om een aparte functie of macro te gebruiken, omdat je dat vaak zult noemen.

ErrorCode is slechts een opsomming van het gegevenstype cl_int voor C #, C / C ++ kan de int-waarde vergelijken met vooraf gedefinieerde foutconstanten zoals hier vermeld: https://www.khronos.org/registry/OpenCL/sdk/1.0/docs/man/ xhtml / errors.html

Je wilt misschien ook controleren of het apparaat alle benodigde functies ondersteunt, anders kunnen je kernels crashen tijdens runtime. U kunt een apparaatcapaciteit opvragen met

Cl.GetDeviceInfo(_device, DeviceInfo.ImageSupport, out err)

In dit voorbeeld wordt aan het apparaat gevraagd of het afbeeldingsfuncties kan uitvoeren. Voor de volgende en laatste stap moeten we onze context samenstellen uit de verzamelde apparaten.

_context = Cl.CreateContext(null, 1, new[] { _device }, ContextNotify, IntPtr.Zero, out err);

Er is hier wat aan de hand. Voor mensen met C / C ++ is IntPtr een pointeradres in C #. Ik zal me hier concentreren op de belangrijke onderdelen.

  • De tweede parameter definieert het aantal apparaten dat u wilt gebruiken
  • De derde parameter is een array van die apparaten (of een pointer in C / C ++)
  • En de derde parameter is een functiepointer voor een callback-functie. Deze functie wordt gebruikt wanneer er fouten optreden binnen de context.

Voor verder gebruik moet u uw gebruikte apparaten en de context ergens bewaren.

Wanneer u al uw OpenCL-interactie hebt voltooid, moet u de context opnieuw vrijgeven met

Cl.ReleaseContext(_context);

Uw kernel compileren

Kernels kunnen tijdens runtime op het doelapparaat worden gecompileerd. Daarvoor heb je nodig

  • de kernel broncode
  • het doelapparaat waarop moet worden gecompileerd
  • een context gebouwd met het doelapparaat

Een snelle terminologie-update: een programma bevat een verzameling kernels. Je kunt een programma beschouwen als een compleet C / C ++ / C # bronbestand, terwijl kernels de verschillende functieleden van dat bestand zijn.

Eerst moet u een programma maken op basis van uw broncode.

var program = Cl.CreateProgramWithSource(_context, 1, new[] { source }, null, out err);

Je kunt meerdere bronbestanden combineren in één programma en ze samen compileren, waardoor je kernels in verschillende bestanden kunt hebben en ze in één keer kunt compileren.

In de volgende stap moet u het programma op uw doelapparaat compileren.

err = Cl.BuildProgram(program, 1, new[] { _device }, string.Empty, null, IntPtr.Zero);

Nu komt hier een kleine waarschuwing: de foutcode vertelt u alleen of de functie-aanroep zelf succesvol was, maar niet of uw code daadwerkelijk is gecompileerd. Om dat te verifiëren, moeten we wat aanvullende informatie opvragen

BuildStatus status;
status = Cl.GetProgramBuildInfo(program, _device, ProgramBuildInfo.Status, out err).CastTo<BuildStatus>();
if (status != BuildStatus.Success) {
    var log = Cl.GetProgramBuildInfo(program, _device, ProgramBuildInfo.Log, out err);
}

C / C ++ mensen kunnen de cast aan het einde negeren en het geretourneerde gehele getal vergelijken met de bijbehorende constante.

De eerste oproep controleert of onze build daadwerkelijk succesvol was. Zo niet, dan kunnen we een logboek achterhalen en precies zien waar dingen fout zijn gegaan. Zie de opmerkingen voor enkele veelvoorkomende pitfals met betrekking tot verschillende platforms.

Zodra het programma is gebouwd, moet je je verschillende kernels uit het gecompileerde programma extraheren. Om dit te doen creëer je je kernels met

_kernel = Cl.CreateKernel(_program, kernel, out err);

waarbij 'kernel' een string van de kernelnaam is. Als je klaar bent met je kernel, moet je deze vrijgeven met

Cl.ReleaseKernel(_kernel);

Een opdrachtwachtrij maken

Als u een bewerking op uw apparaten wilt starten, hebt u voor elk apparaat een opdrachtwachtrij nodig. De wachtrij houdt verschillende oproepen bij die u naar het doelapparaat hebt gedaan en houdt ze in volgorde. De meeste opdrachten kunnen ook worden uitgevoerd in de blokkerende of niet-blokkerende modus.

Een wachtrij maken is vrij eenvoudig:

_queue = Cl.CreateCommandQueue(_context, _device, CommandQueueProperties.None, out err);

De basisinteractie met uw opdrachtwachtrij is om verschillende bewerkingen op te halen die u wilt uitvoeren, bijv. Gegevens van en naar uw apparaat kopiëren en een kernel starten.

Wanneer u klaar bent met het gebruik van de opdrachtwachtrij, moet u de wachtrij met een oproep naar vrijgeven

Cl.ReleaseCommandQueue(_queue);

De kernel uitvoeren

Dus nu komen we bij de echte dingen, het uitvoeren van je kernels op het parallelle apparaat. Lees meer over de grondbeginselen van de hardware om de kernelverzending volledig te begrijpen.

Eerst moet u de kernelargumenten instellen voordat u de kernel daadwerkelijk aanroept. Dit gebeurt via

err = Cl.SetKernelArg(_kernel, $argumentIndex, $argument);

Als u niet elk argument instelt voordat u de kernel start, zal de kernel mislukken.

Voordat we onze kernel daadwerkelijk lanceren, moeten we de 'globale werkgrootte' en de 'lokale werkgrootte' berekenen.

de globale werkgrootte is het totale aantal threads dat op uw GPU wordt gestart. De lokale werkgrootte is het aantal draden in elk draadblok. De lokale werkgrootte kan worden weggelaten als de kernel geen speciale vereisten nodig heeft. Maar als de lokale werkgrootte wordt gegeven, moet de globale werkgrootte een veelvoud zijn van de lokale werkgrootte.

De werkgroottes kunnen eendimensionaal, tweedimensionaal of driedimensionaal zijn. De keuze hoeveel dimensies je wilt, is helemaal aan jou en je kunt kiezen wat het beste bij je algoritme past.

Nu we onze werkgrootte hebben besloten, kunnen we de kernel aanroepen.

Event clevent;
err = Cl.EnqueueNDRangeKernel(_queue, _kernel, $dimensions, null, $globalWorkSize, $localWorkSize, 0, null, out clevent);

De $ -dimensies definiëren ons gewenste aantal dimensies, $ globalWorkSize is een reeks dimensies $ met de globale werkgrootte en dezelfde voor $ localWorkSize. Het laatste argument geeft je een object dat je momenteel uitgevoerde kernel vertegenwoordigt.



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