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:
#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-esimerkkiKuten 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.