GPU -ohjelmointi C ++: lla

Gpu Programming With C



Tässä oppaassa tutkimme GPU -ohjelmoinnin tehoa C ++: lla. Kehittäjät voivat odottaa uskomatonta suorituskykyä C ++: n avulla, ja GPU: n ilmiömäisen tehon käyttäminen matalan tason kielellä voi tuottaa joitain nopeimmista käytettävissä olevista laskutoimituksista.

Vaatimukset

Vaikka kaikki koneet, jotka pystyvät käyttämään nykyaikaista Linux-versiota, voivat tukea C ++ -kääntäjää, tarvitset NVIDIA-pohjaisen grafiikkasuorittimen tämän harjoituksen seuraamiseksi. Jos sinulla ei ole GPU: ta, voit luoda GPU-käyttöisen ilmentymän Amazon Web Services -palvelussa tai muulla valitsemallasi pilvipalveluntarjoajalla.







Jos valitset fyysisen koneen, varmista, että NVIDIA -ohjaimet on asennettu. Ohjeet tähän löydät täältä: https://linuxhint.com/install-nvidia-drivers-linux/



Ohjaimen lisäksi tarvitset CUDA -työkalupakin. Tässä esimerkissä käytämme Ubuntu 16.04 LTS: ää, mutta useimpien suurten jakelujen ladattavia tiedostoja on saatavana seuraavasta URL -osoitteesta: https://developer.nvidia.com/cuda-downloads



Ubuntulle valitsisit .deb -pohjaisen latauksen. Ladatussa tiedostossa ei ole oletusarvoisesti .deb -laajennusta, joten suosittelen nimeämään sen uudelleen .deb -tiedostopäätteeksi. Sen jälkeen voit asentaa:





sudo dpkg -ipaketin nimi.deb

Sinua pyydetään todennäköisesti asentamaan GPG -avain, ja jos on, noudata annettuja ohjeita.

Kun olet tehnyt sen, päivitä arkistosi:



sudo apt-get päivitys
sudo apt-get installihmeitä-ja

Kun olet valmis, suosittelen uudelleenkäynnistystä varmistaaksesi, että kaikki on ladattu oikein.

GPU -kehityksen edut

Suorittimet käsittelevät monia erilaisia ​​tuloja ja lähtöjä ja sisältävät laajan valikoiman toimintoja, jotka eivät ainoastaan ​​käsittele laajaa ohjelmatarpeiden valikoimaa vaan myös hallitsevat erilaisia ​​laitteistokokoonpanoja. Ne käsittelevät myös muistia, välimuistia, järjestelmäväylää, segmentointia ja IO -toimintoja, mikä tekee niistä kaikkien kauppojen liittimen.

GPU: t ovat päinvastoin - ne sisältävät monia yksittäisiä prosessoreita, jotka keskittyvät hyvin yksinkertaisiin matemaattisiin toimintoihin. Tämän vuoksi he käsittelevät tehtäviä monta kertaa nopeammin kuin suorittimet. Erikoistumalla skalaaritoimintoihin (toiminto, joka ottaa yhden tai useamman tulon mutta palauttaa vain yhden lähdön) he saavuttavat äärimmäisen suorituskyvyn äärimmäisen erikoistumisen kustannuksella.

Esimerkkikoodi

Esimerkkikoodissa lisätään vektorit yhteen. Olen lisännyt koodin CPU- ja GPU -version nopeuden vertailua varten.
gpu-example.cpp sisältö alla:

#include 'cuda_runtime.h'
#sisältää
#sisältää
#sisältää
#sisältää
#sisältää

typedeftuntia::chrono::high_resolution_clockKello;

#define ITER 65535

// CPU -versio vektorin lisäystoiminnosta
mitätönvector_add_cpu(int *,int *b,int *c,intn) {
inti;

// Lisää vektorielementit a ja b vektoriin c
varten (i= 0;i<n; ++i) {
c[i] =kohteeseen[i] +b[i];
}
}

// Vektorin lisätoiminnon GPU -versio
__global__mitätönvector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intn) {
inti=threadIdx.x;
// Ei silmukkaa varten, koska CUDA -ajonaikainen
// pujotan tämän ITER kertaa
gpu_c[i] =gpu_a[i] +gpu_b[i];
}

inttärkein() {

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

kohteeseen= (int *)malloc(ITER* koko(int));
b= (int *)malloc(ITER* koko(int));
c= (int *)malloc(ITER* koko(int));

// Tarvitsemme GPU: n käytettävissä olevia muuttujia,
// niin cudaMallocManaged tarjoaa nämä
cudaMallocManaged(&gpu_a, ITER* koko(int));
cudaMallocManaged(&gpu_b, ITER* koko(int));
cudaMallocManaged(&gpu_c, ITER* koko(int));

varten (inti= 0;i<ITER; ++i) {
kohteeseen[i] =i;
b[i] =i;
c[i] =i;
}

// Soita CPU -toiminto ja ajastaa se
autocpu_start=Kello::nyt();
vector_add_cpu(a, b, c, ITER);
autocpu_end=Kello::nyt();
tuntia::kustannus << 'vector_add_cpu:'
<<tuntia::chrono::kesto_lähetys<tuntia::chrono::nanosekuntia>(cpu_end-cpu_start).Kreivi()
<< 'nanosekuntia. n'';

// Soita GPU -toimintoon ja ajastin
// Kolminkertaiset kulmajarrut ovat CUDA -ajonaikainen laajennus, joka mahdollistaa
// välitettävän CUDA -ytimen kutsun parametrit.
// Tässä esimerkissä ohitamme yhden säielohkon ITER -säikeillä.
autogpu_start=Kello::nyt();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
autogpu_end=Kello::nyt();
tuntia::kustannus << 'vector_add_gpu:'
<<tuntia::chrono::kesto_lähetys<tuntia::chrono::nanosekuntia>(gpu_end-gpu_start).Kreivi()
<< 'nanosekuntia. n'';

// Vapauta GPU-toimintoihin perustuvat muistinvaraukset
cudaFree(kohteeseen);
cudaFree(b);
cudaFree(c);

// Vapauta CPU-toimintoihin perustuvat muistinvaraukset
vapaa(kohteeseen);
vapaa(b);
vapaa(c);

palata 0;
}

Tee tiedosto sisältö alla:

INC= -I/usr/paikallinen/ihmeitä/sisältää
NVCC=/usr/paikallinen/ihmeitä/olen/nvcc
NVCC_OPT= -std = c ++yksitoista

kaikki:
$(NVCC)$(NVCC_OPT)gpu-example.cpp-taigpu-esimerkki

puhdas:
-rm -fgpu-esimerkki

Voit suorittaa esimerkin kääntämällä sen:

tehdä

Suorita sitten ohjelma:

./gpu-esimerkki

Kuten näette, CPU -versio (vector_add_cpu) toimii huomattavasti hitaammin kuin GPU -versio (vector_add_gpu).

Jos ei, saatat joutua säätämään ITP: n määritelmän gpu-example.cu suurempana. Tämä johtuu siitä, että GPU: n asennusaika on pidempi kuin jotkut pienemmät suoritinintensiiviset silmukat. Huomasin, että 65535 toimii hyvin koneellani, mutta kilometrimäärä voi vaihdella. Kuitenkin, kun poistat tämän kynnyksen, GPU on dramaattisesti nopeampi kuin suoritin.

Johtopäätös

Toivottavasti olet oppinut paljon johdannostamme GPU -ohjelmointiin C ++: lla. Yllä olevalla esimerkillä ei saada paljon aikaan, mutta esitetyt käsitteet tarjoavat kehyksen, jonka avulla voit sisällyttää ideasi ja vapauttaa GPU: n tehon.