GPU programavimas naudojant C ++

Gpu Programming With C



Šiame vadove mes ištirsime GPU programavimo galią naudojant C ++. Kūrėjai gali tikėtis neįtikėtino našumo naudojant „C ++“, o prieiga prie fenomenalios GPU galios žemo lygio kalba gali suteikti greičiausią šiuo metu prieinamą skaičiavimą.

Reikalavimai

Nors bet kuri mašina, galinti paleisti modernią „Linux“ versiją, gali palaikyti „C ++“ kompiliatorių, jums reikės NVIDIA pagrįsto GPU, kad galėtumėte atlikti šį pratimą. Jei neturite GPU, galite atskirti GPU pagrįstą egzempliorių „Amazon Web Services“ ar kitame pasirinktame debesies paslaugų teikėju.







Jei pasirinksite fizinę mašiną, įsitikinkite, kad turite įdiegtas NVIDIA patentuotas tvarkykles. Instrukcijas tam 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 daugumoje pagrindinių platinimų galima atsisiųsti šį URL: https://developer.nvidia.com/cuda-downloads



Jei naudojate „Ubuntu“, turėtumėte pasirinkti .deb pagrįstą atsisiuntimą. Atsisiunčiamas failas pagal numatytuosius nustatymus neturės .deb plėtinio, todėl rekomenduoju jį pervadinti, kad pabaigoje būtų .deb. Tada galite įdiegti naudodami:





sudo dpkg -ipaketo pavadinimas.deb

Tikriausiai būsite paraginti įdiegti GPG raktą ir, jei taip, vykdykite pateiktas instrukcijas.

Kai tai padarysite, atnaujinkite saugyklas:



sudo apt-get atnaujinimas
sudo apt-get installstebuklai-ir

Baigę rekomenduoju iš naujo paleisti, kad įsitikintumėte, jog viskas tinkamai įkelta.

GPU kūrimo privalumai

Centriniai procesoriai tvarko daugybę skirtingų įėjimų ir išėjimų ir turi daugybę funkcijų, skirtų ne tik patenkinti platų programų 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ų, orientuotų į labai paprastas matematines funkcijas. Dėl šios priežasties jie užduotis apdoroja daug kartų greičiau nei procesoriai. Specializuodamiesi skaliarinėse funkcijose (funkcija, kuri naudoja vieną ar daugiau įėjimų, bet grąžina tik vieną išvestį), jos pasiekia ypatingą našumą ypatingos specializacijos kaina.

Pavyzdinis kodas

Kodo pavyzdyje kartu pridedame vektorius. Pridėjau CPU ir GPU kodo versiją, kad galėčiau palyginti greitį.
gpu-example.cpp turinys žemiau:

#include 'cuda_runtime.h'
#įtraukti
#įtraukti
#įtraukti
#įtraukti
#įtraukti

typedefvalandų::chrono::high_resolution_clockLaikrodis;

#define ITER 65535

// Vektorų pridėjimo funkcijos CPU versija
tuštumavector_add_cpu(tarpt *į,tarpt *b,tarpt *c,tarptn) {
tarpti;

// Pridėkite vektorinius elementus a ir b prie vektoriaus c
dėl (i= 0;i<n; ++i) {
c[i] =į[i] +b[i];
}
}

// Vektorų pridėjimo funkcijos GPU versija
__global__tuštumavector_add_gpu(tarpt *gpu_a,tarpt *gpu_b,tarpt *gpu_c,tarptn) {
tarpti=threadIdx.x;
// Ne, reikia ciklo, nes CUDA vykdymo laikas
// temą ITER kartų
gpu_c[i] =gpu_a[i] +gpu_b[i];
}

tarptpagrindinis() {

tarpt *į,*b,*c;
tarpt *gpu_a,*gpu_b,*gpu_c;

į= (tarpt *)malloc(ITER* dydis(tarpt));
b= (tarpt *)malloc(ITER* dydis(tarpt));
c= (tarpt *)malloc(ITER* dydis(tarpt));

// Mums reikia kintamųjų, prieinamų GPU,
// taip pateikia cudaMallocManaged
cudaMallocManaged(&gpu_a, ITER* dydis(tarpt));
cudaMallocManaged(&gpu_b, ITER* dydis(tarpt));
cudaMallocManaged(&gpu_c, ITER* dydis(tarpt));

dėl (tarpti= 0;i<ITER; ++i) {
į[i] =i;
b[i] =i;
c[i] =i;
}

// Iškvieskite CPU funkciją ir nustatykite laiką
automatiniscpu_start=Laikrodis::dabar();
vector_add_cpu(a, b, c, ITER);
automatiniscpu_end=Laikrodis::dabar();
valandų::kaina << 'vector_add_cpu:'
<<valandų::chrono::trukmės transliacija<valandų::chrono::nanosekundžių>(cpu_end-cpu_start).skaičiuoti()
<< 'nanosekundės. n';

// Iškvieskite GPU funkciją ir nustatykite laiką
// Trijų kampų stabdžiai yra CUDA vykdymo laiko pratęsimas, leidžiantis
// perduoti CUDA branduolio iškvietimo parametrai.
// Šiame pavyzdyje mes perduodame vieną siūlų bloką su ITER gijomis.
automatinisgpu_start=Laikrodis::dabar();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
automatinisgpu_end=Laikrodis::dabar();
valandų::kaina << 'vector_add_gpu:'
<<valandų::chrono::trukmės transliacija<valandų::chrono::nanosekundžių>(gpu_end-gpu_start).skaičiuoti()
<< 'nanosekundės. n';

// Atlaisvinkite GPU funkcija pagrįstą atminties paskirstymą
cudaFree(į);
cudaFree(b);
cudaFree(c);

// Atlaisvinkite CPU funkcija pagrįstą atminties paskirstymą
Laisvas(į);
Laisvas(b);
Laisvas(c);

grįžti 0;
}

Padaryti failą turinys žemiau:

INC= -Aš/usr/vietinis/stebuklai/įtraukti
NVCC=/usr/vietinis/stebuklai/esu/nvcc
NVCC_OPT= -std = c ++vienuolika

visi:
$(NVCC)$(NVCC_OPT)gpu-example.cpp-arbagpu pavyzdys

švarus:
-rm -fgpu pavyzdys

Norėdami paleisti pavyzdį, sukompiliuokite jį:

padaryti

Tada paleiskite programą:

./gpu pavyzdys

Kaip matote, procesoriaus versija (vector_add_cpu) veikia žymiai lėčiau nei GPU versija (vector_add_gpu).

Jei ne, gali tekti koreguoti ITER apibrėžimą, esantį gpu-example.cu, į didesnį skaičių. Taip yra dėl to, kad GPU sąrankos laikas yra ilgesnis nei kai kurių mažesnių CPU reikalaujančių kilpų. Radau, kad 65535 gerai veikia mano mašinoje, tačiau jūsų rida gali skirtis. Tačiau, kai pašalinsite šią ribą, GPU bus žymiai greitesnis nei procesorius.

Išvada

Tikiuosi, kad daug sužinojote iš mūsų įvado į GPU programavimą naudojant C ++. Aukščiau pateiktas pavyzdys nėra labai sėkmingas, tačiau pateiktos sąvokos suteikia pagrindą, kurį galite panaudoti, kad įtrauktumėte savo idėjas, kad išlaisvintumėte savo GPU galią.