Programació GPU amb C ++

Gpu Programming With C



En aquesta guia, explorarem la potència de la programació de GPU amb C ++. Els desenvolupadors poden esperar un rendiment increïble amb C ++ i accedir a la potència fenomenal de la GPU amb un llenguatge de baix nivell pot produir alguns dels càlculs més ràpids disponibles actualment.

Requisits

Tot i que qualsevol màquina capaç d’executar una versió moderna de Linux pot admetre un compilador C ++, necessitareu una GPU basada en NVIDIA per seguir aquest exercici. Si no teniu cap GPU, podeu crear una instància alimentada per GPU a Amazon Web Services o a un altre proveïdor de núvol que trieu.







Si trieu una màquina física, assegureu-vos que teniu instal·lats els controladors propietaris de NVIDIA. Podeu trobar instruccions per a això aquí: https://linuxhint.com/install-nvidia-drivers-linux/



A més del controlador, necessitareu el conjunt d’eines CUDA. En aquest exemple, farem servir Ubuntu 16.04 LTS, però hi ha baixades disponibles per a la majoria de distribucions importants a l’URL següent: https://developer.nvidia.com/cuda-downloads



Per a Ubuntu, escolliríeu la descàrrega basada en .deb. El fitxer descarregat no tindrà una extensió .deb per defecte, així que us recomano canviar el nom per tenir-ne un .deb al final. A continuació, podeu instal·lar amb:





suo dpkg -inom-paquet.deb

És probable que se us demani que instal·leu una clau GPG i, en cas afirmatiu, seguiu les instruccions proporcionades per fer-ho.

Un cop fet això, actualitzeu els repositoris:



suo apt-get update
suo apt-get installmiracles-i

Un cop fet, recomano reiniciar per assegurar-vos que tot està correctament carregat.

Els avantatges del desenvolupament de GPU

Les CPU gestionen moltes entrades i sortides diferents i contenen un ampli assortiment de funcions no només per fer front a un ampli assortiment de necessitats del programa, sinó també per gestionar diferents configuracions de maquinari. També gestionen la memòria, la memòria cau, el bus del sistema, la segmentació i la funcionalitat IO, cosa que els converteix en un jack de tots els oficis.

Les GPU són el contrari: contenen molts processadors individuals enfocats a funcions matemàtiques molt senzilles. Per això, processen tasques moltes vegades més ràpid que les CPU. En especialitzar-se en funcions escalars (una funció que pren una o més entrades però només retorna una sola sortida), aconsegueixen un rendiment extrem a costa d’una especialització extrema.

Codi d'exemple

Al codi d'exemple, afegim vectors junts. He afegit una versió de la CPU i la GPU del codi per comparar la velocitat.
gpu-example.cpp continguts a continuació:

#include 'cuda_runtime.h'
#incloure
#incloure
#incloure
#incloure
#incloure

typedefhores::crono::high_resolution_clockRellotge;

#defineix ITER 65535

// Versió de CPU de la funció d'afegir vector
buitvector_add_cpu(int *a,int *b,int *c,intn) {
intjo;

// Afegiu els elements vectorials a i b al vector c
per (jo= 0;jo<n; ++jo) {
c[jo] =a[jo] +b[jo];
}
}

// Versió GPU de la funció d'afegir vector
__global__buitvector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intn) {
intjo=threadIdx.x;
// No es necessita cap bucle perquè el temps d'execució de CUDA
// enfilarà aquest ITER vegades
gpu_c[jo] =gpu_a[jo] +gpu_b[jo];
}

intprincipal() {

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

a= (int *)malloc(ITER* mida de(int));
b= (int *)malloc(ITER* mida de(int));
c= (int *)malloc(ITER* mida de(int));

// Necessitem variables accessibles a la GPU,
// de manera que cudaMallocManaged els proporciona
cudaMallocManaged(&gpu_a, ITER* mida de(int));
cudaMallocManaged(&gpu_b, ITER* mida de(int));
cudaMallocManaged(&gpu_c, ITER* mida de(int));

per (intjo= 0;jo<ITER; ++jo) {
a[jo] =jo;
b[jo] =jo;
c[jo] =jo;
}

// Truqueu a la funció CPU i temporitzeu-la
automàticcpu_start=Rellotge::ara();
vector_add_cpu(a, b, c, ITER);
automàticcpu_end=Rellotge::ara();
hores::cost << 'vector_add_cpu:'
<<hores::crono::durada_cast<hores::crono::nanosegons>(cpu_end-cpu_start).comptar()
<< 'nanosegons. n';

// Truqueu a la funció GPU i temporitzeu-la
// Els frens de triple angle són una extensió de temps d'execució CUDA que permet
// es passaran els paràmetres d'una trucada del nucli CUDA.
// En aquest exemple, passem un bloc de fils amb fils ITER.
automàticgpu_start=Rellotge::ara();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
automàticgpu_end=Rellotge::ara();
hores::cost << 'vector_add_gpu:'
<<hores::crono::durada_cast<hores::crono::nanosegons>(gpu_end-gpu_start).comptar()
<< 'nanosegons. n';

// Allibereu les assignacions de memòria basades en la funció GPU
cudaFree(a);
cudaFree(b);
cudaFree(c);

// Allibereu les assignacions de memòria basades en la funció CPU
gratuït(a);
gratuït(b);
gratuït(c);

tornar 0;
}

Makefile continguts a continuació:

INC= -Jo/usr/local/miracles/incloure
NVCC=/usr/local/miracles/sóc/nvcc
NVCC_OPT= -std = c ++11

tot:
$(NVCC)$(NVCC_OPT)gpu-example.cpp-ogpu-exemple

net:
-rm -fgpu-exemple

Per executar l'exemple, compileu-lo:

fer

A continuació, executeu el programa:

./gpu-exemple

Com podeu veure, la versió de la CPU (vector_add_cpu) funciona considerablement més lenta que la versió de la GPU (vector_add_gpu).

Si no, és possible que hagueu d'ajustar la definició de l'ITER a gpu-example.cu a un nombre superior. Això es deu al fet que el temps de configuració de la GPU és més llarg que alguns bucles més petits intensius en CPU. He trobat 65535 per funcionar bé a la meva màquina, però el vostre quilometratge pot variar. Tanmateix, un cop esborreu aquest llindar, la GPU serà molt més ràpida que la CPU.

Conclusió

Espero que hagueu après moltes coses de la nostra introducció a la programació de GPU amb C ++. L’exemple anterior no s’aconsegueix molt, però els conceptes demostrats proporcionen un marc que podeu utilitzar per incorporar les vostres idees per donar sortida al poder de la vostra GPU.