GPU-programmering met C++

Gpu Programming With C



In deze handleiding onderzoeken we de kracht van GPU-programmering met C++. Ontwikkelaars kunnen ongelooflijke prestaties verwachten met C++, en toegang tot de fenomenale kracht van de GPU met een taal op laag niveau kan een van de snelste berekeningen opleveren die momenteel beschikbaar zijn.

Vereisten

Hoewel elke machine die een moderne versie van Linux kan draaien een C++-compiler kan ondersteunen, heb je een op NVIDIA gebaseerde GPU nodig om deze oefening te volgen. Als je geen GPU hebt, kun je een GPU-aangedreven instantie starten in Amazon Web Services of een andere cloudprovider naar keuze.







Als u een fysieke machine kiest, zorg er dan voor dat u de eigen NVIDIA-stuurprogramma's hebt geïnstalleerd. Instructies hiervoor vind je hier: https://linuxhint.com/install-nvidia-drivers-linux/



Naast de driver heb je de CUDA-toolkit nodig. In dit voorbeeld gebruiken we Ubuntu 16.04 LTS, maar er zijn downloads beschikbaar voor de meeste grote distributies op de volgende URL: https://developer.nvidia.com/cuda-downloads



Voor Ubuntu zou je de op .deb gebaseerde download kiezen. Het gedownloade bestand heeft standaard geen .deb-extensie, dus ik raad aan om het te hernoemen naar een .deb aan het einde. Vervolgens kunt u installeren met:





sudo dpkg -lpakketnaam.deb

U wordt waarschijnlijk gevraagd om een ​​GPG-sleutel te installeren, en als dat het geval is, volgt u de instructies om dit te doen.

Zodra je dat hebt gedaan, werk je je repositories bij:



sudo apt-get update
sudo apt-get installwonderen-en

Als je klaar bent, raad ik aan om opnieuw op te starten om ervoor te zorgen dat alles correct is geladen.

De voordelen van GPU-ontwikkeling

CPU's verwerken veel verschillende inputs en outputs en bevatten een groot assortiment aan functies voor niet alleen het omgaan met een breed assortiment aan programmabehoeften, maar ook voor het beheren van verschillende hardwareconfiguraties. Ze behandelen ook geheugen, caching, de systeembus, segmentering en IO-functionaliteit, waardoor ze een manusje van alles zijn.

GPU's zijn het tegenovergestelde - ze bevatten veel individuele processors die zijn gericht op zeer eenvoudige wiskundige functies. Hierdoor verwerken ze taken vele malen sneller dan CPU's. Door zich te specialiseren in scalaire functies (een functie die een of meer invoer nodig heeft maar slechts één uitvoer retourneert), bereiken ze extreme prestaties ten koste van extreme specialisatie.

Voorbeeldcode:

In de voorbeeldcode tellen we vectoren bij elkaar op. Ik heb een CPU- en GPU-versie van de code toegevoegd voor snelheidsvergelijking.
gpu-voorbeeld.cpp inhoud hieronder:

#include 'cuda_runtime.h'
#erbij betrekken
#erbij betrekken
#erbij betrekken
#erbij betrekken
#erbij betrekken

typedefuur::chrono::hoge_resolutie_klokKlok;

#define ITER 65535

// CPU-versie van de vector-toevoegfunctie
leegtevector_add_cpu(int *tot,int *B,int *C,intN) {
intl;

// Voeg de vectorelementen a en b toe aan de vector c
voor (l= 0;l<N; ++l) {
C[l] =tot[l] +B[l];
}
}

// GPU-versie van de vector-toevoegfunctie
__globaal__leegtevector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intN) {
intl=draadIdx.x;
// Geen for-lus nodig omdat de CUDA-runtime
// zal deze ITER-tijden inrijgen
gpu_c[l] =gpu_a[l] +gpu_b[l];
}

inthoofd() {

int *tot,*B,*C;
int *gpu_a,*gpu_b,*gpu_c;

tot= (int *)malloc(ITER* De grootte van(int));
B= (int *)malloc(ITER* De grootte van(int));
C= (int *)malloc(ITER* De grootte van(int));

// We hebben variabelen nodig die toegankelijk zijn voor de GPU,
// dus cudaMallocManaged biedt deze
cudaMallocManaged(&gpu_a, ITER* De grootte van(int));
cudaMallocManaged(&gpu_b, ITER* De grootte van(int));
cudaMallocManaged(&gpu_c, ITER* De grootte van(int));

voor (intl= 0;l<ITER; ++l) {
tot[l] =l;
B[l] =l;
C[l] =l;
}

// Roep de CPU-functie aan en time it
autocpu_start=Klok::nu();
vector_add_cpu(a, b, c, ITER);
autocpu_end=Klok::nu();
uur::kosten << 'vector_add_cpu: '
<<uur::chrono::duration_cast<uur::chrono::nanoseconden>(cpu_end-cpu_start).Graaf()
<< ' nanoseconden.N';

// Roep de GPU-functie aan en time it
// De triple angle brakets is een CUDA runtime-extensie die het mogelijk maakt:
// parameters van een CUDA-kernelaanroep die moet worden doorgegeven.
// In dit voorbeeld passeren we één threadblok met ITER-threads.
autogpu_start=Klok::nu();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchroniseren();
autogpu_end=Klok::nu();
uur::kosten << 'vector_add_gpu: '
<<uur::chrono::duration_cast<uur::chrono::nanoseconden>(gpu_end-gpu_start).Graaf()
<< ' nanoseconden.N';

// Bevrijd de op GPU-functie gebaseerde geheugentoewijzingen
cudaFree(tot);
cudaFree(B);
cudaFree(C);

// Bevrijd de op CPU-functie gebaseerde geheugentoewijzingen
vrij(tot);
vrij(B);
vrij(C);

opbrengst 0;
}

Makefile inhoud hieronder:

INC=-I/usr/lokaal/wonderen/erbij betrekken
NVCC=/usr/lokaal/wonderen/ben/nvcc
NVCC_OPT=-std=c++elf

alle:
$(NVCC)$(NVCC_OPT)gpu-voorbeeld.cpp-ofgpu-voorbeeld

schoon:
-rm -Fgpu-voorbeeld

Om het voorbeeld uit te voeren, compileert u het:

maken

Voer vervolgens het programma uit:

./gpu-voorbeeld

Zoals je kunt zien, werkt de CPU-versie (vector_add_cpu) aanzienlijk langzamer dan de GPU-versie (vector_add_gpu).

Als dat niet het geval is, moet u mogelijk de ITER-definitie in gpu-example.cu naar een hoger nummer aanpassen. Dit komt doordat de GPU-insteltijd langer is dan bij sommige kleinere CPU-intensieve lussen. Ik vond 65535 goed werken op mijn machine, maar uw kilometerstand kan variëren. Als u deze drempel eenmaal hebt overschreden, is de GPU echter aanzienlijk sneller dan de CPU.

Conclusie

Ik hoop dat je veel hebt geleerd van onze introductie in GPU-programmering met C++. Het bovenstaande voorbeeld brengt niet veel tot stand, maar de gedemonstreerde concepten bieden een raamwerk dat u kunt gebruiken om uw ideeën op te nemen om de kracht van uw GPU te ontketenen.