GPU -programmering med C ++

Gpu Programming With C



I denne vejledning vil vi undersøge kraften i GPU -programmering med C ++. Udviklere kan forvente utrolig ydeevne med C ++, og adgang til den fænomenale kraft i GPU'en med et lavt sprog kan give nogle af de hurtigste beregninger, der er tilgængelige i øjeblikket.

Krav

Selvom enhver maskine, der er i stand til at køre en moderne version af Linux, kan understøtte en C ++-kompilator, skal du bruge en NVIDIA-baseret GPU til at følge denne øvelse. Hvis du ikke har en GPU, kan du spinde en GPU-drevet instans op i Amazon Web Services eller en anden cloud-udbyder efter eget valg.







Hvis du vælger en fysisk maskine, skal du sørge for at have NVIDIAs proprietære drivere installeret. Du kan finde vejledning til dette her: https://linuxhint.com/install-nvidia-drivers-linux/



Ud over driveren skal du bruge CUDA -værktøjssættet. I dette eksempel vil vi bruge Ubuntu 16.04 LTS, men der er downloads til rådighed for de fleste større distributioner på følgende URL: https://developer.nvidia.com/cuda-downloads



For Ubuntu ville du vælge .deb -baseret download. Den downloadede fil har som standard ikke en .deb -udvidelse, så jeg anbefaler at omdøbe den til at have en .deb i slutningen. Derefter kan du installere med:





sudo dpkg -jegpakkenavn.deb

Du vil sandsynligvis blive bedt om at installere en GPG -nøgle, og følg i givet fald instruktionerne for at gøre det.

Når du har gjort det, skal du opdatere dine lagre:



sudo apt-get opdatering
sudo apt-get installmirakler-og

Når det er gjort, anbefaler jeg at genstarte for at sikre, at alt er korrekt indlæst.

Fordelene ved GPU -udvikling

CPU'er håndterer mange forskellige input og output og indeholder et stort udvalg af funktioner til ikke kun at håndtere et bredt sortiment af programbehov, men også til at styre forskellige hardwarekonfigurationer. De håndterer også hukommelse, caching, systembussen, segmentering og IO -funktionalitet, hvilket gør dem til et jack of all trades.

GPU'er er det modsatte - de indeholder mange individuelle processorer, der er fokuseret på meget enkle matematiske funktioner. På grund af dette behandler de opgaver mange gange hurtigere end CPU'er. Ved at specialisere sig i skalarfunktioner (en funktion, der tager et eller flere input, men kun returnerer et enkelt output), opnår de ekstrem ydeevne på bekostning af ekstrem specialisering.

Eksempelkode

I eksempelkoden tilføjer vi vektorer sammen. Jeg har tilføjet en CPU og GPU -version af koden til hastighedssammenligning.
gpu-eksempel.cpp indhold herunder:

#include 'cuda_runtime.h'
#omfatte
#omfatte
#omfatte
#omfatte
#omfatte

typedeftimer::chrono::høj_opløsning_urUr;

#define ITER 65535

// CPU -version af vektor tilføjelsesfunktionen
ugyldigvector_add_cpu(int *til,int *b,int *c,intn) {
intjeg;

// Tilføj vektorelementerne a og b til vektoren c
til (jeg= 0;jeg<n; ++jeg) {
c[jeg] =til[jeg] +b[jeg];
}
}

// GPU -version af vektor tilføjelsesfunktionen
__global__ugyldigvector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intn) {
intjeg=threadIdx.x;
// Ingen for loop nødvendig, fordi CUDA -runtime
// vil tråde denne ITER gange
gpu_c[jeg] =gpu_a[jeg] +gpu_b[jeg];
}

intvigtigste() {

int *til,*b,*c;
int *gpu_a,*gpu_b,*gpu_c;

til= (int *)malloc(ITER* størrelse på(int));
b= (int *)malloc(ITER* størrelse på(int));
c= (int *)malloc(ITER* størrelse på(int));

// Vi har brug for variabler, der er tilgængelige for GPU'en,
// så cudaMallocManaged leverer disse
cudaMallocManaged(&gpu_a, ITER* størrelse på(int));
cudaMallocManaged(&gpu_b, ITER* størrelse på(int));
cudaMallocManaged(&gpu_c, ITER* størrelse på(int));

til (intjeg= 0;jeg<ITER; ++jeg) {
til[jeg] =jeg;
b[jeg] =jeg;
c[jeg] =jeg;
}

// Ring til CPU -funktionen, og giv den tid
autocpu_start=Ur::nu();
vector_add_cpu(a, b, c, ITER);
autocpu_end=Ur::nu();
timer::koste << 'vector_add_cpu:'
<<timer::chrono::varighed_udsendelse<timer::chrono::nanosekunder>(cpu_end-cpu_start).tælle()
<< 'nanosekunder. n';

// Ring til GPU -funktionen, og giv den tid
// Triple vinkel bremserne er en CUDA runtime forlængelse, der tillader
// parametre for et CUDA -kernekald, der skal sendes.
// I dette eksempel passerer vi en trådblok med ITER -tråde.
autogpu_start=Ur::nu();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
autogpu_end=Ur::nu();
timer::koste << 'vector_add_gpu:'
<<timer::chrono::varighed_udsendelse<timer::chrono::nanosekunder>(gpu_end-gpu_start).tælle()
<< 'nanosekunder. n';

// Frigør GPU-funktionsbaserede hukommelsestildelinger
cudaFree(til);
cudaFree(b);
cudaFree(c);

// Frigør CPU-funktionsbaserede hukommelsestildelinger
gratis(til);
gratis(b);
gratis(c);

Vend tilbage 0;
}

Makefile indhold herunder:

INC= -Jeg/usr/lokal/mirakler/omfatte
NVCC=/usr/lokal/mirakler/er/nvcc
NVCC_OPT= -std = c ++elleve

alle:
$(NVCC)$(NVCC_OPT)gpu-eksempel.cpp-ellergpu-eksempel

ren:
-rm -fgpu-eksempel

For at køre eksemplet skal du kompilere det:

lave

Kør derefter programmet:

./gpu-eksempel

Som du kan se, kører CPU -versionen (vector_add_cpu) betydeligt langsommere end GPU -versionen (vector_add_gpu).

Hvis ikke, skal du muligvis justere ITER-definitionen i gpu-example.cu til et højere tal. Dette skyldes, at GPU-opsætningstiden er længere end nogle mindre CPU-intensive sløjfer. Jeg fandt 65535 til at fungere godt på min maskine, men din kilometertal kan variere. Men når du har ryddet denne tærskel, er GPU'en dramatisk hurtigere end CPU'en.

Konklusion

Jeg håber, at du har lært meget af vores introduktion til GPU -programmering med C ++. Eksemplet ovenfor opnår ikke meget, men de demonstrerede koncepter giver en ramme, som du kan bruge til at inkorporere dine ideer for at frigøre kraften i din GPU.