Apžvalga
Šiame vadove mes išnagrinėsime GPU programavimo galingumą su C++. Kūrėjai gali tikėtis neįtikėtino našumo naudodami „C ++“, o pasiekę fenomenalią GPU galią žemo lygio kalba gali gauti greičiausią šiuo metu prieinamą skaičiavimą.
Reikalavimai
Nors bet kuri mašina, galinti paleisti modernią „Linux“ versiją, gali palaikyti kompiliatorių C ++, jums reikės NVIDIA pagrįsto GPU, kad galėtumėte sekti šį pratimą. Jei neturite GPU, galite sujungti GPU veikiantį egzempliorių „Amazon Web Services“ ar kitame pasirinktame debesų teikėjuje.
Jei pasirinksite fizinę mašiną, įsitikinkite, kad įdiegėte nuosavų „NVIDIA“ tvarkyklių. Nurodymus galite rasti čia: https: // linuxhint.com / install-nvidia-drivers-linux /
Be tvarkyklės, jums reikės CUDA įrankių rinkinio. Šiame pavyzdyje naudosime „Ubuntu 16“.04 LTS, tačiau daugeliui pagrindinių paskirstymų galima atsisiųsti šiuo URL: https: // kūrėjas.nvidia.com / cuda-downloads
Jei norite naudoti „Ubuntu“, pasirinkite .deb pagrįstas atsisiuntimas. Atsisiųstame faile nebus .deb plėtinys pagal numatytuosius nustatymus, todėl rekomenduoju jį pervadinti į .deb pabaigoje. Tada galite įdiegti naudodami:
sudo dpkg -i paketo pavadinimas.debGreičiausiai būsite paraginti įdiegti GPG raktą ir, jei taip, vykdykite pateiktas instrukcijas.
Tai atlikę atnaujinkite saugyklas:
sudo apt-get atnaujinimassudo apt-get install cuda -y
Kai tai bus padaryta, rekomenduoju iš naujo paleisti, kad viskas būtų tinkamai įkrauta.
GPU kūrimo nauda
Centriniai procesoriai valdo daugybę skirtingų įėjimų ir išėjimų ir juose yra didelis funkcijų asortimentas, skirtas ne tik patenkinti platų programos poreikių asortimentą, bet ir valdyti įvairias aparatūros konfigūracijas. Jie taip pat tvarko atmintį, talpyklą, sistemos magistralę, segmentavimą ir IO funkcijas, todėl jie yra visų sandorių lizdas.
GPU yra priešingai - juose yra daug atskirų procesorių, kurie orientuoti į labai paprastas matematines funkcijas. Dėl to jie užduotis apdoroja daug kartų greičiau nei procesoriai. Specializuodamiesi skaliarinėms funkcijoms (funkcijai reikalinga viena ar kelios įvestys, bet pateikiama tik viena išvestis), jos pasiekia ypatingą našumą kraštutinės specializacijos kaina.
Kodo pavyzdys
Kodo pavyzdyje mes įtraukiame vektorius kartu. Greičio palyginimui pridėjau kodo CPU ir GPU versiją.
gpu-pavyzdys.cpp žemiau pateiktas turinys:
# įtraukti
# įtraukti
# įtraukti
# įtraukti
# įtraukti
typedef std :: chrono :: high_resolution_clock Laikrodis;
#define ITER 65535
// Vektoriaus pridėjimo funkcijos procesoriaus versija
negaliojantis vector_add_cpu (int * a, int * b, int * c, int n)
int i;
// Pridėkite vektoriaus elementus a ir b prie vektoriaus c
už (i = 0; i < n; ++i)
c [i] = a [i] + b [i];
// GPU vektoriaus pridėjimo funkcijos versija
__global__ negaliojantis vector_add_gpu (int * gpu_a, int * gpu_b, int * gpu_c, int n)
int i = threadIdx.x;
// Ne reikia kilpai, nes CUDA vykdymo laikas
// siųs tai ITER kartus
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 * (int) dydis);
b = (int *) malloc (ITER * (int) dydis);
c = (int *) malloc (ITER * (int) dydis);
// Mums reikia kintamųjų, prieinamų GPU,
// taip cudaMallocManaged pateikia šiuos
cudaMallocManaged (& gpu_a, ITER * sizeof (int));
cudaMallocManaged (& gpu_b, ITER * sizeof (int));
cudaMallocManaged (& gpu_c, ITER * sizeof (int));
už (int i = 0; i < ITER; ++i)
a [i] = i;
b [i] = i;
c [i] = i;
// Iškvieskite centrinio procesoriaus funkciją ir nustatykite jos laiką
auto cpu_start = Laikrodis :: dabar ();
vector_add_cpu (a, b, c, ITER);
auto cpu_end = Laikrodis :: dabar ();
std :: cout << "vector_add_cpu: "
<< std::chrono::duration_cast
<< " nanoseconds.\n";
// Iškvieskite GPU funkciją ir nustatykite jos laiką
// Trigubo kampo stabdžiai yra CUDA vykdymo laiko pratęsimas, leidžiantis
// turi būti perduoti CUDA branduolio skambučio parametrai.
// Šiame pavyzdyje mes perduodame vieną gijų bloką su ITER gijomis.
auto gpu_start = Laikrodis :: dabar ();
vector_add_gpu <<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize ();
auto gpu_end = Laikrodis :: dabar ();
std :: cout << "vector_add_gpu: "
<< std::chrono::duration_cast
<< " nanoseconds.\n";
// Atlaisvinkite GPU funkcija pagrįstą atminties paskirstymą
cudaFree (a);
cudaFree (b);
cudaFree (c);
// Atlaisvinkite atminties paskirstymą pagal procesoriaus funkciją
nemokamai (a);
laisvas (b);
laisvas (c);
grąžinti 0;
„Makefile“ žemiau pateiktas turinys:
INC = -I / usr / local / cuda / įtrauktiNVCC = / usr / local / cuda / bin / nvcc
NVCC_OPT = -std = c ++ 11
visi:
$ (NVCC) $ (NVCC_OPT) GPU pavyzdys.cpp -o gpu-pavyzdys
švarus:
-rm -f gpu-pavyzdys
Norėdami paleisti pavyzdį, sudarykite jį:
padarytiTada paleiskite programą:
./ gpu-pavyzdysKaip matote, procesoriaus versija (vector_add_cpu) veikia žymiai lėčiau nei GPU versija (vector_add_gpu).
Jei ne, gali tekti pakoreguoti ITER apibrėžimą „gpu-example“.cu į didesnį skaičių. Taip yra dėl to, kad GPU sąrankos laikas yra ilgesnis nei kai kurių mažesnių intensyvaus procesoriaus kilpų. Radau, kad mano mašinoje gerai veikia 65535, tačiau jūsų rida gali skirtis. Tačiau, kai jūs išvalysite šį slenkstį, GPU yra žymiai greitesnis nei procesorius.
Išvada
Tikiuosi, kad daug išmokote iš mūsų įvedimo į GPU programavimą su C++. Ankstesniu pavyzdžiu nepasiekiama daug, tačiau parodytos koncepcijos suteikia pagrindą, kurį galite naudoti įtraukdami savo idėjas, kad atskleistumėte savo GPU galią.