GPU programmēšana ar C ++

Gpu Programming With C



Šajā rokasgrāmatā mēs izpētīsim GPU programmēšanas jaudu, izmantojot C ++. Izstrādātāji var sagaidīt neticamu veiktspēju, izmantojot C ++, un piekļuve GPU fenomenālajai jaudai ar zema līmeņa valodu var nodrošināt dažus no ātrākajiem pašlaik pieejamajiem aprēķiniem.

Prasības

Lai gan jebkura mašīna, kas spēj darbināt mūsdienīgu Linux versiju, var atbalstīt C ++ kompilatoru, lai veiktu šo uzdevumu, jums būs nepieciešams NVIDIA bāzes GPU. Ja jums nav GPU, varat izveidot ar GPU darbināmu instanci Amazon Web Services vai citā izvēlētajā mākoņa nodrošinātājā.







Ja izvēlaties fizisku mašīnu, lūdzu, pārliecinieties, vai ir instalēti NVIDIA patentētie draiveri. Norādījumus par to varat atrast šeit: https://linuxhint.com/install-nvidia-drivers-linux/



Papildus draiverim jums būs nepieciešams CUDA rīku komplekts. Šajā piemērā mēs izmantosim Ubuntu 16.04 LTS, taču lielākajai daļai izplatījumu ir pieejamas lejupielādes šajā URL: https://developer.nvidia.com/cuda-downloads



Ubuntu gadījumā jūs izvēlaties lejupielādi uz .deb. Lejupielādētajam failam pēc noklusējuma nebūs .deb paplašinājuma, tāpēc iesaku to pārdēvēt, lai beigās būtu .deb. Pēc tam jūs varat instalēt ar:





sudo dpkg -ipakotnes nosaukums.deb

Jums, iespējams, tiks piedāvāts instalēt GPG atslēgu, un, ja tā, izpildiet sniegtos norādījumus.

Kad esat to izdarījis, atjauniniet krātuves:



sudo apt-get atjauninājums
sudo apt-get instalētbrīnumus-un

Kad tas ir izdarīts, es iesaku restartēt, lai pārliecinātos, ka viss ir pareizi ielādēts.

GPU izstrādes priekšrocības

CPU apstrādā daudzas dažādas ieejas un izejas, un tajās ir liels funkciju klāsts, kas paredzēts ne tikai plaša programmu vajadzību klāsta risināšanai, bet arī dažādu aparatūras konfigurāciju pārvaldībai. Viņi arī apstrādā atmiņu, kešatmiņu, sistēmas kopni, segmentēšanu un IO funkcionalitāti, padarot tos par visu darījumu domkratu.

GPU ir pretēji - tie satur daudzus atsevišķus procesorus, kas ir vērsti uz ļoti vienkāršām matemātiskām funkcijām. Šī iemesla dēļ viņi apstrādā uzdevumus daudzas reizes ātrāk nekā CPU. Specializējoties skalārajās funkcijās (funkcija, kas veic vienu vai vairākas ievades, bet atgriež tikai vienu izvadi), tās sasniedz ārkārtēju veiktspēju par ārkārtējas specializācijas cenu.

Koda paraugs

Piemēra kodā mēs pievienojam vektorus kopā. Ātruma salīdzināšanai esmu pievienojis koda CPU un GPU versiju.
gpu-example.cpp saturs zemāk:

#include 'cuda_runtime.h'
#iekļaut
#iekļaut
#iekļaut
#iekļaut
#iekļaut

typedefstundas::hron::high_resolution_clockPulkstenis;

#define ITER 65535

// Vektora pievienošanas funkcijas CPU versija
spēkā neesošsvector_add_cpu(int *uz,int *b,int *c,intn) {
inti;

// Pievienojiet vektora elementus a un b vektoram c
priekš (i= 0;i<n; ++i) {
c[i] =uz[i] +b[i];
}
}

// vektora pievienošanas funkcijas GPU versija
__global__spēkā neesošsvector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intn) {
inti=threadIdx.x;
// Nē ciklam, jo ​​nepieciešams CUDA izpildlaiks
// pavediens būs ITER reizes
gpu_c[i] =gpu_a[i] +gpu_b[i];
}

intgalvenais() {

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

uz= (int *)malloc(ITER* izmērs(int));
b= (int *)malloc(ITER* izmērs(int));
c= (int *)malloc(ITER* izmērs(int));

// Mums ir nepieciešami GPU pieejami mainīgie,
// so cudaMallocManaged nodrošina šos
cudaMallocManaged(&gpu_a, ITER* izmērs(int));
cudaMallocManaged(&gpu_b, ITER* izmērs(int));
cudaMallocManaged(&gpu_c, ITER* izmērs(int));

priekš (inti= 0;i<ITER; ++i) {
uz[i] =i;
b[i] =i;
c[i] =i;
}

// Izsauciet CPU funkciju un iestatiet laiku
autocpu_start=Pulkstenis::tagad();
vector_add_cpu(a, b, c, ITER);
autocpu_end=Pulkstenis::tagad();
stundas::izmaksas << 'vector_add_cpu:'
<<stundas::hron::ilgums_raide<stundas::hron::nanosekundes>(cpu_end-cpu_start).saskaitīt()
<< 'nanosekundes. n';

// Izsauciet GPU funkciju un iestatiet laiku
// Trīskāršā leņķa bremzes ir CUDA darbības laika pagarinājums, kas ļauj
// jānodod CUDA kodola izsaukuma parametri.
// Šajā piemērā mēs nododam vienu pavedienu bloku ar ITER pavedieniem.
autogpu_start=Pulkstenis::tagad();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
autogpu_end=Pulkstenis::tagad();
stundas::izmaksas << 'vector_add_gpu:'
<<stundas::hron::ilgums_raide<stundas::hron::nanosekundes>(gpu_end-gpu_start).saskaitīt()
<< 'nanosekundes. n';

// Atbrīvojiet uz GPU balstītas atmiņas piešķiršanu
cudaFree(uz);
cudaFree(b);
cudaFree(c);

// Atbrīvojiet uz CPU funkcijām balstītus atmiņas piešķīrumus
bezmaksas(uz);
bezmaksas(b);
bezmaksas(c);

atgriezties 0;
}

Makefile saturs zemāk:

INC= -Es/usr/vietējais/brīnumus/iekļaut
NVCC=/usr/vietējais/brīnumus/esmu/nvcc
NVCC_OPT= -std = c ++vienpadsmit

visi:
$(NVCC)$(NVCC_OPT)gpu-example.cpp-vaigpu-piemērs

tīrs:
-rm -fgpu-piemērs

Lai izpildītu piemēru, apkopojiet to:

veidot

Pēc tam palaidiet programmu:

./gpu-piemērs

Kā redzat, CPU versija (vector_add_cpu) darbojas ievērojami lēnāk nekā GPU versija (vector_add_gpu).

Ja nē, iespējams, būs jāpielāgo ITP definīcija gpu-example.cu uz lielāku skaitli. Tas ir saistīts ar to, ka GPU iestatīšanas laiks ir garāks nekā dažas mazākas CPU ietilpīgas cilpas. Es atklāju, ka 65535 labi darbojas manā mašīnā, taču jūsu nobraukums var atšķirties. Tomēr, tiklīdz esat notīrījis šo slieksni, GPU ir ievērojami ātrāks nekā centrālais procesors.

Secinājums

Es ceru, ka jūs esat daudz iemācījušies no mūsu ievada GPU programmēšanā, izmantojot C ++. Iepriekš minētais piemērs nesniedz daudz, bet parādītie jēdzieni nodrošina sistēmu, kuru varat izmantot, lai iekļautu savas idejas, lai izmantotu GPU jaudu.