GPU -programmering med C ++

Gpu Programming With C



I denne guiden vil vi utforske kraften i GPU -programmering med C ++. Utviklere kan forvente utrolig ytelse med C ++, og tilgang til den fenomenale kraften til GPU-en med et lavt språk kan gi noen av de raskeste beregningene som er tilgjengelige.

Krav

Selv om enhver maskin som kan kjøre en moderne versjon av Linux kan støtte en C ++-kompilator, trenger du en NVIDIA-basert GPU for å følge denne øvelsen. Hvis du ikke har en GPU, kan du spinne opp en GPU-drevet forekomst i Amazon Web Services eller en annen skyleverandør etter eget valg.







Hvis du velger en fysisk maskin, må du kontrollere at du har NVIDIAs proprietære drivere installert. Du finner instruksjoner for dette her: https://linuxhint.com/install-nvidia-drivers-linux/



I tillegg til driveren trenger du CUDA -verktøysettet. I dette eksemplet bruker vi Ubuntu 16.04 LTS, men det er nedlastinger tilgjengelig for de fleste større distribusjoner på følgende URL: https://developer.nvidia.com/cuda-downloads



For Ubuntu velger du .deb -basert nedlasting. Den nedlastede filen vil ikke ha en .deb -utvidelse som standard, så jeg anbefaler å gi den et nytt navn til slutten. Deretter kan du installere med:





sudo dpkg -Jegpakkenavn.deb

Du vil sannsynligvis bli bedt om å installere en GPG -nøkkel, og følg i så fall instruksjonene som er gitt for å gjøre det.

Når du har gjort det, oppdaterer du depotene dine:



sudo apt-get oppdatering
sudo apt-get installmirakler-og

Når det er gjort, anbefaler jeg at du starter på nytt for å sikre at alt er riktig lastet inn.

Fordelene med GPU -utvikling

CPUer håndterer mange forskjellige innganger og utganger og inneholder et stort utvalg av funksjoner for ikke bare å håndtere et bredt utvalg av programbehov, men også for å håndtere varierende maskinvarekonfigurasjoner. De håndterer også minne, caching, systembussen, segmentering og IO -funksjonalitet, noe som gjør dem til en god fordel.

GPUer er det motsatte - de inneholder mange individuelle prosessorer som er fokusert på veldig enkle matematiske funksjoner. På grunn av dette behandler de oppgaver mange ganger raskere enn CPUer. Ved å spesialisere seg på skalarfunksjoner (en funksjon som tar en eller flere innganger, men bare returnerer en enkelt utgang), oppnår de ekstrem ytelse på bekostning av ekstrem spesialisering.

Eksempelkode

I eksempelkoden legger vi til vektorer sammen. Jeg har lagt til en CPU- og GPU -versjon av koden for hastighetssammenligning.
gpu-eksempel.cpp innholdet nedenfor:

#include 'cuda_runtime.h'
#inkludere
#inkludere
#inkludere
#inkludere
#inkludere

typedeftimer::chrono::høy_oppløsning_klokkeKlokke;

#define ITER 65535

// CPU -versjon av vektortilleggsfunksjonen
tomromvector_add_cpu(int *til,int *b,int *c,intn) {
intJeg;

// Legg til vektorelementene a og b i vektoren c
til (Jeg= 0;Jeg<n; ++Jeg) {
c[Jeg] =til[Jeg] +b[Jeg];
}
}

// GPU -versjon av vektortilleggsfunksjonen
__global__tomromvector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intn) {
intJeg=threadIdx.x;
// Ingen for sløyfe nødvendig fordi CUDA -kjøretiden
// vil tråden denne ITER ganger
gpu_c[Jeg] =gpu_a[Jeg] +gpu_b[Jeg];
}

inthoved-() {

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

til= (int *)malloc(ITER* størrelsen av(int));
b= (int *)malloc(ITER* størrelsen av(int));
c= (int *)malloc(ITER* størrelsen av(int));

// Vi trenger variabler som er tilgjengelige for GPU,
// så gir cudaMallocManaged disse
cudaMallocManaged(&gpu_a, ITER* størrelsen av(int));
cudaMallocManaged(&gpu_b, ITER* størrelsen av(int));
cudaMallocManaged(&gpu_c, ITER* størrelsen av(int));

til (intJeg= 0;Jeg<ITER; ++Jeg) {
til[Jeg] =Jeg;
b[Jeg] =Jeg;
c[Jeg] =Jeg;
}

// Ring til CPU -funksjonen og sett den på tid
autocpu_start=Klokke::();
vector_add_cpu(a, b, c, ITER);
autocpu_end=Klokke::();
timer::koste << 'vector_add_cpu:'
<<timer::chrono::varighet_kast<timer::chrono::nanosekunder>(cpu_end-cpu_start).telle()
<< 'nanosekunder. n';

// Ring GPU -funksjonen og sett den på tid
// Trippelvinkelbremsene er en CUDA -kjøretidsforlengelse som tillater
// parametere for et CUDA -kjerneanrop som skal sendes.
// I dette eksemplet passerer vi en trådblokk med ITER -tråder.
autogpu_start=Klokke::();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
autogpu_end=Klokke::();
timer::koste << 'vector_add_gpu:'
<<timer::chrono::varighet_kast<timer::chrono::nanosekunder>(gpu_end-gpu_start).telle()
<< 'nanosekunder. n';

// Frigjør GPU-funksjonsbaserte minnetildelinger
cudaFree(til);
cudaFree(b);
cudaFree(c);

// Frigjøre CPU-funksjonsbaserte minnetildelinger
gratis(til);
gratis(b);
gratis(c);

komme tilbake 0;
}

Lag fil innholdet nedenfor:

INC= -Jeg/usr/lokal/mirakler/inkludere
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 å kjøre eksemplet, kompiler det:

gjøre

Kjør deretter programmet:

./gpu-eksempel

Som du kan se, går CPU -versjonen (vector_add_cpu) betydelig tregere enn GPU -versjonen (vector_add_gpu).

Hvis ikke, må du kanskje justere ITER-definisjonen i gpu-example.cu til et høyere tall. Dette skyldes at GPU-installasjonstiden er lengre enn noen mindre CPU-intensive sløyfer. Jeg fant 65535 som fungerte bra på maskinen min, men kjørelengden din kan variere. Men når du tømmer denne terskelen, er GPU -en dramatisk raskere enn CPU -en.

Konklusjon

Jeg håper du har lært mye av introduksjonen til GPU -programmering med C ++. Eksemplet ovenfor oppnår ikke mye, men konseptene som demonstreres gir et rammeverk som du kan bruke til å inkorporere ideene dine for å slippe løs kraften i GPU -en din.