Pregled
V tem priročniku bomo raziskali moč GPU programiranja s C++. Razvijalci lahko pričakujejo neverjetno zmogljivost s C ++, dostop do neverjetne moči grafičnega procesorja z nizkim nivojem pa lahko prinese nekaj najhitrejših trenutno izračuna.
Zahteve
Medtem ko lahko kateri koli stroj, ki lahko poganja moderno različico Linuxa, podpira prevajalnik C ++, boste za izvajanje te vaje potrebovali grafični procesor na osnovi NVIDIA. Če nimate GPU-ja, lahko v Amazon Web Services ali drugem ponudniku oblaka po vaši želji zavrtite primerek, ki ga poganja GPU.
Č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: // razvijalec.nvidia.com / cuda-downloads
Za Ubuntu bi izbrali .prenos na osnovi deb. Prenesena datoteka ne bo imela datoteke .deb privzeto, zato priporočam, da jo preimenujete v .deb na koncu. Nato lahko namestite z:
sudo dpkg -i ime-paketa.debVerjetno boste pozvani, da namestite ključ GPG, in če je tako, sledite navodilom za to.
Ko to storite, posodobite svoje repozitorije:
posodobitev sudo apt-getsudo apt-get namestite cuda -y
Ko končate, priporočam ponovni zagon, da zagotovite, da je vse pravilno naloženo.
Prednosti razvoja GPU
CPU obdelujejo veliko različnih vhodov in izhodov in vsebujejo širok izbor funkcij, ne samo za obravnavanje širokega izbora programskih potreb, temveč tudi za upravljanje različnih konfiguracij strojne opreme. Obvladujejo tudi pomnilnik, predpomnjenje, sistemsko vodilo, segmentiranje in funkcijo IO, zaradi česar so vse vrste poslov.
GPU so nasprotni - vsebujejo veliko posameznih procesorjev, ki so osredotočeni na zelo preproste matematične funkcije. Zaradi tega obdelajo naloge velikokrat hitreje kot CPE. S specializacijo za skalarne funkcije (funkcija, ki sprejme enega ali več vhodov, a vrne samo en izhod), dosežejo izjemne zmogljivosti za ceno ekstremne specializacije.
Primer kode
V primeru kode seštejemo vektorje. Za primerjavo hitrosti sem dodal različico kode za CPU in GPU.
gpu-primer.cpp vsebina spodaj:
#include
#include
#include
#include
#include
typedef std :: chrono :: high_resolution_clock Clock;
#define ITER 65535
// CPU različica funkcije vektorskega dodajanja
void vector_add_cpu (int * a, int * b, int * c, int n)
int i;
// V vektor c dodamo vektorska elementa a in b
za (i = 0; i < n; ++i)
c [i] = a [i] + b [i];
// GPU različica funkcije vektorskega dodajanja
__global__ void vector_add_gpu (int * gpu_a, int * gpu_b, int * gpu_c, int n)
int i = threadIdx.x;
// Ne za zanko, ker je izvajalno okolje CUDA
// bo potegnil to ITER-krat
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 * velikost (int));
b = (int *) malloc (ITER * velikost (int));
c = (int *) malloc (ITER * velikost (int));
// Potrebujemo spremenljivke, dostopne GPU,
// tako cudaMallocManaged to zagotavlja
cudaMallocManaged (& gpu_a, ITER * sizeof (int));
cudaMallocManaged (& gpu_b, ITER * sizeof (int));
cudaMallocManaged (& gpu_c, ITER * sizeof (int));
za (int i = 0; i < ITER; ++i)
a [i] = i;
b [i] = i;
c [i] = i;
// Pokličite funkcijo CPU in določite čas
auto cpu_start = Clock :: now ();
vector_add_cpu (a, b, c, ITER);
auto cpu_end = Clock :: now ();
std :: cout << "vector_add_cpu: "
<< std::chrono::duration_cast
<< " nanoseconds.\n";
// Pokličite funkcijo GPU in določite čas
// Trikotne nosilce je razširitev izvajalnega okolja CUDA, ki omogoča
// parametri klica jedra CUDA, ki se posredujejo.
// V tem primeru prenašamo en blok niti z nitmi ITER.
auto gpu_start = Ura :: zdaj ();
vektor_add_gpu <<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize ();
auto gpu_end = Ura :: zdaj ();
std :: cout << "vector_add_gpu: "
<< std::chrono::duration_cast
<< " nanoseconds.\n";
// Osvobodite dodelitve pomnilnika na podlagi funkcije GPU
cudaFree (a);
cudaFree (b);
cudaFree (c);
// Osvobodite dodelitve pomnilnika na osnovi funkcije CPU
brezplačno (a);
brezplačno (b);
brezplačno (c);
vrnitev 0;
Makefile vsebina spodaj:
INC = -I / usr / local / cuda / includeNVCC = / usr / local / cuda / bin / nvcc
NVCC_OPT = -std = c ++ 11
vse:
$ (NVCC) $ (NVCC_OPT) primer gpu.cpp -o gpu-primer
čisto:
-rm -f gpu-primer
Če želite zagnati primer, ga prevedite:
narediteNato zaženite program:
./ gpu-primerKot lahko vidite, različica CPU (vector_add_cpu) deluje precej počasneje kot različica GPU (vector_add_gpu).
V nasprotnem primeru boste morda morali prilagoditi opredelitev ITER v primeru gpu.cu na večje število. Razlog za to je, da je čas nastavitve GPU daljši od nekaterih manjših zank, ki intenzivno uporabljajo CPU. Ugotovil sem, da 65535 dobro deluje na mojem stroju, vendar se vaša kilometrina lahko razlikuje. Ko pa očistite ta prag, je GPU bistveno hitrejši od CPU.
Zaključek
Upam, da ste se veliko naučili iz našega uvajanja v programiranje GPU z C++. Zgornji primer ne prinaša veliko, vendar predstavljeni koncepti zagotavljajo ogrodje, ki ga lahko uporabite za vključitev svojih idej in sprostitev moči vašega grafičnega procesorja.