GPU -programmering med C ++

GPU -programmering med C ++

Oversikt

I denne guiden skal vi utforske kraften i GPU -programmering med C++. Utviklere kan forvente utrolige ytelser med C ++, og tilgang til den fenomenale kraften til GPU med et lavt nivå kan gi noe av den raskeste beregningen som for øyeblikket er tilgjengelige.

Krav

Mens enhver maskin som er i stand til å kjøre en moderne versjon av Linux kan støtte en C ++ -kompilator, trenger du en NVIDIA-basert GPU for å følge med med denne øvelsen. Hvis du ikke har en GPU, kan du snurre 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 forsikre deg om at du har installert NVIDIA proprietære drivere. Du kan finne instruksjoner for dette her: https: // linuxhint.com/install-nvidia-drivers-linux/

I tillegg til sjåføren, trenger du CUDA -verktøysettet. I dette eksemplet bruker vi Ubuntu 16.04 LTS, men det er nedlastinger tilgjengelig for de fleste store distribusjoner ved følgende URL: https: // utvikler.nvidia.com/cuda-downloads

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

sudo dpkg -i package -name.Deb

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

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

sudo apt-get oppdatering
sudo apt -get installer cuda -y

Når jeg er ferdig, anbefaler jeg å starte på nytt for å sikre at alt er riktig lastet.

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, hurtigbufring, systembussen, segmentering og IO -funksjonalitet, noe som gjør dem til en knekt av alle handler.

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 i skalarfunksjoner (en funksjon som tar en eller flere innganger, men som bare returnerer en enkelt utgang), oppnår de ekstrem ytelse på bekostning av ekstrem spesialisering.

Eksempelkode

I eksempelkoden legger vi vektorer sammen. Jeg har lagt til en CPU- og GPU -versjon av koden for hastighetssammenligning.
GPU-Eksempel.CPP Innhold nedenfor:

#include "cuda_runtime.h "
#inkludere
#inkludere
#inkludere
#inkludere
#inkludere
typedef std :: chrono :: high_resolution_clock klokke;
#Define Iter 65535
// CPU -versjonen av vektor add -funksjonen
void vector_add_cpu (int *a, int *b, int *c, int n)
int jeg;
// Legg vektorelementene A og B i vektoren C
for (i = 0; i < n; ++i)
c [i] = a [i] + b [i];


// gpu -versjon av vektor add -funksjonen
__global__ void Vector_add_gpu (int *gpu_a, int *gpu_b, int *gpu_c, int n)
int i = threadIdx.x;
// nei for sløyfe nødvendig fordi CUDA -runtime
// vil trå denne ITER -tidene
gpu_c [i] = gpu_a [i] + gpu_b [i];

int main ()
int *a, *b, *c;
int *gpu_a, *gpu_b, *gpu_c;
a = (int *) malloc (iter * sizeof (int));
b = (int *) malloc (iter * sizeof (int));
c = (int *) malloc (iter * sizeof (int));
// vi trenger variabler tilgjengelig for GPU,
// så cudamallocmanaged gir disse
cudamallocmanaged (& gpu_a, iter * sizeof (int));
cudamallocmanaged (& gpu_b, iter * sizeof (int));
cudamallocmanaged (& gpu_c, iter * sizeof (int));
for (int i = 0; i < ITER; ++i)
a [i] = i;
b [i] = i;
c [i] = i;

// Ring CPU -funksjonen og tiden den
auto cpu_start = klokke :: nå ();
vector_add_cpu (a, b, c, iter);
auto cpu_end = klokke :: nå ();
std :: cout << "vector_add_cpu: "
<< std::chrono::duration_cast(CPU_END - CPU_START).telle()
<< " nanoseconds.\n";
// Ring GPU -funksjonen og tiden den
// The Triple Angle Brakets er en CUDA runtime -forlengelse som tillater
// Parametere for en CUDA -kjernen som skal sendes.
// I dette eksemplet passerer vi en trådblokk med iter tråder.
auto gpu_start = klokke :: nå ();
Vector_add_gpu <<<1, ITER>>> (GPU_A, GPU_B, GPU_C, ITER);
cudadevicesynchronize ();
auto gpu_end = klokke :: nå ();
std :: cout << "vector_add_gpu: "
<< std::chrono::duration_cast(gpu_end - gpu_start).telle()
<< " nanoseconds.\n";
// gratis GPU-funksjonsbaserte minnetildeling
cudafree (a);
Cudafree (b);
Cudafree (C);
// gratis CPU-funksjonsbaserte minnetildeling
gratis (a);
gratis (b);
gratis (c);
retur 0;

Makefile Innhold nedenfor:

Inc = -i/usr/local/cuda/include
NVCC =/usr/local/cuda/bin/nvcc
NVCC_OPT = -STD = C ++ 11
alle:
$ (NVCC) $ (NVCC_OPT) GPU-Eksempel.CPP -o GPU -EXample
ren:
-RM -F GPU -EXIMMER

For å kjøre eksemplet, kompiler det:

gjøre

Kjør deretter programmet:

./GPU-Eksempel

Som du kan se, kjører CPU -versjonen (Vector_add_cpu) betydelig tregere enn GPU -versjonen (Vector_add_gpu).

Hvis ikke, kan det hende du må justere ITER-defineren i GPU-eksemplet.cu til et høyere antall. Dette skyldes at GPU-oppsettet er lengre enn noen mindre CPU-intensive løkker. Jeg fant 65535 for å fungere godt på maskinen min, men kjørelengden din kan variere. Når du først har tømt denne terskelen, er GPU imidlertid dramatisk raskere enn CPU.

Konklusjon

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