Programiranje GPU s C ++

Gpu Programming With C



V tem priročniku bomo raziskali moč programiranja GPU s C ++. Razvijalci lahko pričakujejo neverjetne zmogljivosti s C ++, dostop do fenomenalne moči grafičnega procesorja z jezikom na nizki ravni pa lahko prinese nekaj najhitrejših izračunov, ki so trenutno na voljo.

Zahteve

Čeprav vsak stroj, ki lahko izvaja sodobno različico Linuxa, lahko podpira prevajalnik C ++, boste za to vajo potrebovali grafični procesor na osnovi NVIDIA. Če nimate grafičnega procesorja, lahko v Amazon Web Services ali drugem ponudniku oblakov po vaši izbiri zaženete primerek z grafičnim procesorjem.







Če izberete fizični stroj, preverite, ali imate nameščene lastniške gonilnike NVIDIA. Navodila za to najdete tukaj: https://linuxhint.com/install-nvidia-drivers-linux/



Poleg gonilnika boste potrebovali komplet orodij CUDA. V tem primeru bomo uporabili Ubuntu 16.04 LTS, vendar so na voljo prenosi za večino večjih distribucij na naslednjem URL -ju: https://developer.nvidia.com/cuda-downloads



Za Ubuntu bi izbrali prenos na osnovi .deb. Naložena datoteka privzeto ne bo imela razširitve .deb, zato priporočam, da jo preimenujete v .deb na koncu. Nato lahko namestite z:





sudo dpkg -jazime-paketa.deb

Verjetno boste pozvani, da namestite ključ GPG, in če je tako, upoštevajte priložena navodila.

Ko to storite, posodobite svoja skladišča:



sudo apt-get posodobitev
sudo apt-get installčudeži-in

Ko končate, priporočam ponovni zagon, da zagotovite, da je vse pravilno naloženo.

Prednosti razvoja grafičnih procesorjev

CPE upravljajo z različnimi vhodi in izhodi ter vsebujejo velik nabor funkcij za ne le reševanje širokega nabora programskih potreb, temveč tudi za upravljanje različnih konfiguracij strojne opreme. Ukvarjajo se tudi s pomnilnikom, predpomnjenjem, sistemskim vodilom, segmentiranjem in vhodno / izhodno funkcionalnostjo, zaradi česar so jack vseh poslov.

GPU -ji so nasprotni - vsebujejo veliko posameznih procesorjev, ki so osredotočeni na zelo preproste matematične funkcije. Zaradi tega naloge obdelujejo velikokrat hitreje kot procesorji. S specializacijo za skalarne funkcije (funkcija, ki sprejme enega ali več vhodov, vendar vrne le en izhod), dosežejo izjemno zmogljivost na račun skrajne specializacije.

Primer kode

V vzorčni kodi vektorje seštevamo. Za primerjavo hitrosti sem dodal različico kode CPU in GPU.
gpu-example.cpp spodnja vsebina:

#include 'cuda_runtime.h'
#vključi
#vključi
#vključi
#vključi
#vključi

typedefure::chrono::ura visoke ločljivostiUra;

#define ITER 65535

// CPU različica funkcije vektorskega dodajanja
ničnovector_add_cpu(int *do,int *b,int *c,intn) {
intjaz;

// Vektorju c dodamo vektorska elementa a in b
za (jaz= 0;jaz<n; ++jaz) {
c[jaz] =do[jaz] +b[jaz];
}
}

// GPU različica funkcije vektorskega dodajanja
__global__ničnovector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intn) {
intjaz=threadIdx.x;
// Ne potrebujemo zanke for, ker je čas delovanja CUDA
// bo v to temo vložil ITER -krat
gpu_c[jaz] =gpu_a[jaz] +gpu_b[jaz];
}

intglavni() {

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

do= (int *)malloc(ITER* velikostof(int));
b= (int *)malloc(ITER* velikostof(int));
c= (int *)malloc(ITER* velikostof(int));

// Potrebujemo spremenljivke, ki so dostopne GPU -ju,
// torej cudaMallocManaged to zagotavlja
cudaMallocManaged(&gpu_a, ITER* velikostof(int));
cudaMallocManaged(&gpu_b, ITER* velikostof(int));
cudaMallocManaged(&gpu_c, ITER* velikostof(int));

za (intjaz= 0;jaz<ITER; ++jaz) {
do[jaz] =jaz;
b[jaz] =jaz;
c[jaz] =jaz;
}

// Pokličite funkcijo CPU in izmerite čas
samodejnocpu_start=Ura::zdaj();
vector_add_cpu(a, b, c, ITER);
samodejnocpu_end=Ura::zdaj();
ure::stroški << 'vector_add_cpu:'
<<ure::chrono::duration_cast<ure::chrono::nanosekund>(cpu_end-cpu_start).šteti()
<< 'nanosekund. n';

// Pokličite funkcijo GPU in izmerite čas
// Trojni kotni nosilci so razširitev časa izvajanja CUDA, ki omogoča
// parametri klica jedra CUDA, ki jih je treba prenesti.
// V tem primeru posredujemo en blok niti z nitmi ITER.
samodejnogpu_start=Ura::zdaj();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
samodejnogpu_end=Ura::zdaj();
ure::stroški << 'vector_add_gpu:'
<<ure::chrono::duration_cast<ure::chrono::nanosekund>(gpu_end-gpu_start).šteti()
<< 'nanosekund. n';

// Osvobodite dodelitve pomnilnika, ki temeljijo na grafični funkciji
cudaBREZPLAČNO(do);
cudaBREZPLAČNO(b);
cudaBREZPLAČNO(c);

// Osvobodite dodelitve pomnilnika, ki temeljijo na funkciji CPU
prost(do);
prost(b);
prost(c);

vrnitev 0;
}

Makefile spodnja vsebina:

INC= -I/usr/lokalno/čudeži/vključujejo
NVCC=/usr/lokalno/čudeži/zjutraj/nvcc
NVCC_OPT= -std = c ++enajst

vse:
$(NVCC)$(NVCC_OPT)gpu-example.cpp-aligpu-primer

čisto:
-rm -fgpu-primer

Če želite zagnati primer, ga sestavite:

narediti

Nato zaženite program:

./gpu-primer

Kot lahko vidite, različica procesorja (vector_add_cpu) deluje precej počasneje kot različica grafičnega procesorja (vector_add_gpu).

V nasprotnem primeru boste morda morali prilagoditi definicijo ITER v gpu-example.cu na večje število. To je posledica tega, da je čas nastavitve grafičnega procesorja daljši od nekaterih manjših zank, ki zahtevajo veliko procesorja. Ugotovil sem, da 65535 dobro deluje na mojem stroju, vendar se lahko vaša kilometrina razlikuje. Ko pa počistite ta prag, je GPU dramatično hitrejši od CPU -ja.

Zaključek

Upam, da ste se veliko naučili iz našega uvoda v programiranje GPU s C ++. Zgornji primer ne dosega veliko, vendar predstavljeni koncepti ponujajo okvir, ki ga lahko uporabite za vključitev svojih idej, da sprostite moč svojega grafičnega procesorja.