Sök…


Introduktion

Innan man använder OpenCL måste man ställa in sin kod för att använda den. Det här ämnet fokuserar på hur du får opencl up och körs i ditt projekt och kör en grundläggande kärna. Exemplen är baserade på C # -omslaget OpenCL.NET men eftersom omslaget lägger ingen abstraktion till OpenCL kommer koden förmodligen att köras med mycket få ändringar på C / C ++ också.

Samtal i C # kan se ut enligt följande: 'Cl.GetPlatformIDs'. För C-Style OpenCL Api skulle du kalla "clGetPlatformIDs" och för C ++ -stilen en "cl :: GetPlatformIDs"

Anmärkningar

  • NVidia, AMD och Intel har något olika implementeringar av OpenCL men de kända skillnaderna är (för min erfarenhet) begränsade till parenteskrav och implicita avkastningar. Ibland kraschar NVidia din kärna medan du försöker hitta rätt överbelastning för en metod. I det här fallet hjälper det att erbjuda en uttrycklig roll som stöd för GPU. Problemet observerades för runtime-kompilerade kärnor.

  • För att få mer information om de använda samtalen i detta ämne räcker det att google 'OpenCL' följt av funktionsnamnet. Khronos-gruppen har en fullständig dokumentation om alla parametrar och datatyper som finns tillgängliga på deras webbplats.

Initierar målenheten

OpenCL-kärnor kan antingen köras på GPU eller CPU. Detta möjliggör fallback-lösningar, där kunden kan ha ett mycket föråldrat system. Programmeraren kan också välja att begränsa sin funktionalitet till antingen CPU eller GPU.

För att komma igång med OpenCL, behöver du en "Context" och en "Device". Båda är strukturer definierade av OpenCL API (även känd som cl :: Context eller clContext & ~ Device) och definierar den använda målprocessorn.

För att få din enhet och kontext måste du fråga en lista över tillgängliga plattformar, som var och en kan vara värd för flera enheter. En plattform representerar din fysiska GPU och CPU, medan en enhet ytterligare kan skilja de inbyggda datorenheterna. För GPU: er har de flesta plattformar bara en enhet. Men en CPU kan erbjuda en extra integrerad GPU förutom sina CPU-funktioner.

Kontextet hanterar minne, kommandoköer, olika kärnor och program. Ett sammanhang kan antingen begränsas till en enda enhet men också referera till flera enheter.

En snabb API-anmärkning innan vi börjar kodningen: Nästan varje samtal till OpenCL ger dig ett felvärde, antingen som returvärde eller via ett ref-värde (pekaren i C). Nu kan vi komma igång.

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;
        [...]
    }
}

Den här kodavsnittet frågar alla tillgängliga GPU-enheter på systemet. Du kan nu lägga till dem i en lista eller starta ditt sammanhang direkt med den första matchen. Funktionen CheckError (...) är ett enkelt verktyg som kontrollerar om felkoden har framgångsvärdet eller ett annat och kan erbjuda dig en viss loggning. Det rekommenderas att använda en separat funktion eller ett makro, eftersom du kommer att ringa det mycket.

ErrorCode är bara enum på datatypen cl_int för C #, C / C ++ kan jämföra int-värdet med fördefinierade felkonstanter som anges här: https://www.khronos.org/registry/OpenCL/sdk/1.0/docs/man/ xhtml / errors.html

Du kanske också vill kontrollera om enheten stöder alla funktioner som behövs, annars kan dina kärnor krascha vid körning. Du kan fråga om en enhetsfunktion med

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

I det här exemplet frågar enheten om den kan utföra bildfunktioner. För nästa och sista steg måste vi konstruera vårt sammanhang ur de insamlade enheterna.

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

Vissa saker pågår här. För C / C ++ folk är IntPtr en pekaradress i C #. Jag kommer att koncentrera mig på de viktiga delarna här.

  • Den andra parametern definierar antalet enheter du vill använda
  • Den tredje parametern är en matris med dessa enheter (eller en pekare i C / C ++)
  • Och den tredje parametern är en funktionspekare för en återuppringningsfunktion. Denna funktion kommer att användas när fel inträffar i sammanhanget.

För vidare användning måste du bevara dina använda enheter och sammanhanget någonstans.

När du är klar med all din OpenCL-interaktion måste du släppa sammanhanget igen med

Cl.ReleaseContext(_context);

Sammansätta din kärna

Kärnor kan sammanställas vid körning på målenheten. För att göra det behöver du

  • källkällkoden
  • målenheten att kompilera på
  • ett sammanhang byggt med målenheten

En snabb uppdatering av terminologin: Ett program innehåller en samling kärnor. Du kan tänka på ett program som en komplett C / C ++ / C # källfil, medan kärnor är de olika funktionsmedlemmarna i den filen.

Först måste du skapa ett program ur din källkod.

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

Du kan kombinera flera källfiler i ett program och sammanställa dem tillsammans, vilket gör att du kan ha kärnor i olika filer och sammanställa dem tillsammans på en gång.

I nästa steg måste du kompilera programmet på din målenhet.

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

Nu kommer en liten varning: Felkoden säger bara till dig, om själva funktionssamtalet var framgångsrikt men inte om din kod faktiskt kompilerades. För att verifiera det måste vi fråga efter ytterligare information

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 ++ personer kan ignorera rollspelaren i slutet och bara jämföra det returnerade heltalet med motsvarande konstant.

Det första samtalet kontrollerar om vår byggnad faktiskt var framgångsrik. Om inte kan vi återuppta en logg och se exakt var saker gick fel. Se anmärkningarna för några vanliga pitfals om olika plattformar.

När programmet har byggts måste du extrahera dina olika kärnor ur det sammanställda programmet. För att göra det skapar du dina kärnor med

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

där 'kärnan' är en sträng med kärnans namn. När du är klar med din kärna måste du släppa den med

Cl.ReleaseKernel(_kernel);

Skapa en kommandokö

För att initiera alla åtgärder på dina enheter behöver du en kommandokö för varje enhet. Kön håller reda på olika samtal du gjorde till målenheten och håller dem i ordning. De flesta kommandon kan också utföras antingen i blockerande eller icke-blockerande läge.

Att skapa en kö är ganska enkelt:

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

Den grundläggande interaktionen med din kommandokö är att förlägga olika operationer du vill utföra, t.ex. kopiera data till och från din enhet och starta en kärna.

När du är klar med kommandokön måste du släppa kön med ett samtal till

Cl.ReleaseCommandQueue(_queue);

Utför kärnan

Så nu kommer vi till de verkliga sakerna, kör dina kärnor på den parallella enheten. Läs om hårdvarugrundlaget för att förstå kärnans utsändning.

Först måste du ställa in kärnargumenten innan du faktiskt kallar kärnan. Detta görs via

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

Om du inte ställer in alla argument innan kärnan startas, kommer kärnan att misslyckas.

Innan vi faktiskt lanserar vår kärna måste vi beräkna den globala arbetsstorleken och den lokala arbetsstorleken.

den globala arbetsstorleken är det totala antalet trådar som kommer att lanseras på din GPU. Den lokala arbetsstorleken är antalet trådar i varje trådblock. Den lokala arbetsstorleken kan utelämnas om kärnan inte behöver några speciella krav. Men om den lokala arbetsstorleken anges måste den globala arbetsstorleken vara en multipel av den lokala arbetsstorleken.

Arbetsstorlekarna kan antingen vara en-dimensionell, två-dimensionell eller tredimensionell. Valet av hur många dimensioner du vill är helt upp till dig och du kan välja vad som passar din algoritm bäst.

Nu när vi bestämde oss för våra arbetsstorlekar kan vi kalla kärnan.

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

$ -Dimensionerna definierar vårt önskade antal dimensioner, $ globalWorkSize är en matris med storlek $ -dimensioner med den globala arbetsstorleken och densamma för $ localWorkSize. Det sista argumentet ger dig ett objekt som representerar din nuvarande exekverade kärna.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow