Programmation GPU avec C++

Gpu Programming With C



Dans ce guide, nous allons explorer la puissance de la programmation GPU avec C++. Les développeurs peuvent s'attendre à des performances incroyables avec C++, et accéder à la puissance phénoménale du GPU avec un langage de bas niveau peut produire certains des calculs les plus rapides actuellement disponibles.

Conditions

Alors que toute machine capable d'exécuter une version moderne de Linux peut prendre en charge un compilateur C++, vous aurez besoin d'un GPU basé sur NVIDIA pour suivre cet exercice. Si vous n'avez pas de GPU, vous pouvez créer une instance alimentée par GPU dans Amazon Web Services ou un autre fournisseur de cloud de votre choix.







Si vous choisissez une machine physique, assurez-vous que les pilotes propriétaires NVIDIA sont installés. Vous pouvez trouver des instructions pour cela ici : https://linuxhint.com/install-nvidia-drivers-linux/



En plus du pilote, vous aurez besoin de la boîte à outils CUDA. Dans cet exemple, nous utiliserons Ubuntu 16.04 LTS, mais des téléchargements sont disponibles pour la plupart des distributions majeures à l'URL suivante : https://developer.nvidia.com/cuda-downloads



Pour Ubuntu, vous choisiriez le téléchargement basé sur .deb. Le fichier téléchargé n'aura pas d'extension .deb par défaut, je vous recommande donc de le renommer pour avoir un .deb à la fin. Ensuite, vous pouvez installer avec :





sudo dpkg -jenom-paquet.deb

Vous serez probablement invité à installer une clé GPG, et si c'est le cas, suivez les instructions fournies pour le faire.

Une fois cela fait, mettez à jour vos référentiels :



sudo apt-get mise à jour
sudo apt-get installermiracle-et

Une fois cela fait, je vous recommande de redémarrer pour vous assurer que tout est correctement chargé.

Les avantages du développement GPU

Les processeurs gèrent de nombreuses entrées et sorties différentes et contiennent un large éventail de fonctions non seulement pour répondre à un large éventail de besoins de programmes, mais également pour gérer diverses configurations matérielles. Ils gèrent également la mémoire, la mise en cache, le bus système, la segmentation et les fonctionnalités d'E/S, ce qui en fait un touche-à-tout.

Les GPU sont à l'opposé – ils contiennent de nombreux processeurs individuels qui se concentrent sur des fonctions mathématiques très simples. Pour cette raison, ils traitent les tâches beaucoup plus rapidement que les processeurs. En se spécialisant dans les fonctions scalaires (une fonction qui prend une ou plusieurs entrées mais ne renvoie qu'une seule sortie), ils atteignent des performances extrêmes au prix d'une spécialisation extrême.

Exemple de code

Dans l'exemple de code, nous ajoutons des vecteurs ensemble. J'ai ajouté une version CPU et GPU du code pour la comparaison de vitesse.
exemple-gpu.cpp contenu ci-dessous :

#include 'cuda_runtime.h'
#comprendre
#comprendre
#comprendre
#comprendre
#comprendre

typedefles heures::chrono::horloge_haute_résolutionL'horloge;

#définir ITER 65535

// Version CPU de la fonction d'ajout de vecteur
annulervector_add_cpu(entier *à,entier *b,entier *c,entierm) {
entierje;

// Ajoute les éléments vectoriels a et b au vecteur c
pour (je= 0;je<m; ++je) {
c[je] =à[je] +b[je];
}
}

// Version GPU de la fonction d'ajout de vecteur
__global__annulervector_add_gpu(entier *gpu_a,entier *gpu_b,entier *gpu_c,entierm) {
entierje=threadIdx.X;
// Pas de boucle for nécessaire car le runtime CUDA
// va enfiler cet ITER fois
gpu_c[je] =gpu_a[je] +gpu_b[je];
}

entierprincipale() {

entier *à,*b,*c;
entier *gpu_a,*gpu_b,*gpu_c;

à= (entier *)malloc(ITER* taille de(entier));
b= (entier *)malloc(ITER* taille de(entier));
c= (entier *)malloc(ITER* taille de(entier));

// On a besoin de variables accessibles au GPU,
// donc cudaMallocManaged fournit ces
cudaMallocGéré(&gpu_a, ITER* taille de(entier));
cudaMallocGéré(&gpu_b, ITER* taille de(entier));
cudaMallocGéré(&gpu_c, ITER* taille de(entier));

pour (entierje= 0;je<ITER; ++je) {
à[je] =je;
b[je] =je;
c[je] =je;
}

// Appeler la fonction CPU et la chronométrer
autocpu_start=L'horloge::maintenant();
vector_add_cpu(a, b, c, ITER);
autocpu_end=L'horloge::maintenant();
les heures::cout << « vector_add_cpu : »
<<les heures::chrono::duration_cast<les heures::chrono::nanosecondes>(cpu_end-cpu_start).compter()
<< ' nanosecondes. ';

// Appelez la fonction GPU et chronométrez-la
// Le triple angle brackets est une extension d'exécution CUDA qui permet
// paramètres d'un appel noyau CUDA à passer.
// Dans cet exemple, nous passons un bloc de threads avec des threads ITER.
autogpu_start=L'horloge::maintenant();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
autogpu_end=L'horloge::maintenant();
les heures::cout << « vector_add_gpu : »
<<les heures::chrono::duration_cast<les heures::chrono::nanosecondes>(gpu_end-gpu_start).compter()
<< ' nanosecondes. ';

// Libérer les allocations de mémoire basées sur la fonction GPU
cudaGratuit(à);
cudaGratuit(b);
cudaGratuit(c);

// Libérer les allocations de mémoire basées sur la fonction CPU
libre(à);
libre(b);
libre(c);

revenir 0;
}

Makefile contenu ci-dessous :

INC=-Je/usr/local/miracle/comprendre
CNVCC=/usr/local/miracle/un m/nvcc
NVCC_OPT=-std=c++Onze

tous:
$(CNVCC)$(NVCC_OPT)exemple-gpu.cpp-ouexemple-gpu

nettoyer:
-rm -Fexemple-gpu

Pour exécuter l'exemple, compilez-le :

Fabriquer

Exécutez ensuite le programme :

./exemple-gpu

Comme vous pouvez le voir, la version CPU (vector_add_cpu) s'exécute considérablement plus lentement que la version GPU (vector_add_gpu).

Sinon, vous devrez peut-être ajuster la définition ITER dans gpu-example.cu à un nombre plus élevé. Cela est dû au fait que le temps de configuration du GPU est plus long que certaines boucles plus petites gourmandes en CPU. J'ai trouvé que 65535 fonctionnait bien sur ma machine, mais votre kilométrage peut varier. Cependant, une fois ce seuil dépassé, le GPU est considérablement plus rapide que le CPU.

Conclusion

J'espère que vous avez beaucoup appris de notre introduction à la programmation GPU avec C++. L'exemple ci-dessus n'apporte pas grand-chose, mais les concepts présentés fournissent un cadre que vous pouvez utiliser pour incorporer vos idées afin de libérer la puissance de votre GPU.