C++

Programmation GPU avec C++

Programmation GPU avec C++

Aperçu

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 principales distributions à l'URL suivante : https://developer.nvidia.com/cuda-téléchargements

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

sudo dpkg -i nom-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 dépôts :

sudo apt-get mise à jour
sudo apt-get install cuda -y

Une fois cela fait, je recommande de redémarrer pour s'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"
#inclure
#inclure
#inclure
#inclure
#inclure
typedef std::chrono::high_resolution_clock Horloge;
#définir ITER 65535
// Version CPU de la fonction d'ajout de vecteur
void vector_add_cpu(int *a, int *b, int *c, int n)
int je;
// Ajoute les éléments vectoriels a et b au vecteur c
pour (i = 0; je < n; ++i)
c[i] = a[i] + b[i] ;


// Version GPU de la fonction d'ajout de vecteur
__global__ void vector_add_gpu(int *gpu_a, int *gpu_b, int *gpu_c, int n)
int i = threadIdx.X;
// Pas de boucle for nécessaire car le runtime CUDA
// va enfiler cet ITER fois
gpu_c[i] = gpu_a[i] + gpu_b[i] ;

int main()
entier *a, *b, *c;
int *gpu_a, *gpu_b, *gpu_c;
a = (int *)malloc(ITER * sizeof(int));
b = (int *)malloc(ITER * sizeof(int));
c = (int *)malloc(ITER * sizeof(int));
// On a besoin de variables accessibles au GPU,
// donc cudaMallocManaged fournit ces
cudaMallocManaged(&gpu_a, ITER * sizeof(int));
cudaMallocManaged(&gpu_b, ITER * sizeof(int));
cudaMallocManaged(&gpu_c, ITER * sizeof(int));
pour (int i = 0; i < ITER; ++i)
a[i] = je;
b[i] = je;
c[i] = je;

// Appeler la fonction CPU et la chronométrer
auto cpu_start = Clock::now();
vector_add_cpu(a, b, c, ITER);
auto cpu_end = Clock::now();
std::cout << "vector_add_cpu: "
<< std::chrono::duration_cast(cpu_end - cpu_start).compter()
<< " nanoseconds.\n";
// 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 thread avec des threads ITER.
auto gpu_start = Clock::now();
vector_add_gpu <<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
auto gpu_end = Clock::now();
std::cout << "vector_add_gpu: "
<< std::chrono::duration_cast(gpu_end - gpu_start).compter()
<< " nanoseconds.\n";
// Libérer les allocations de mémoire basées sur la fonction GPU
cudaFree(a);
cudaFree(b);
cudaFree(c);
// Libérer les allocations de mémoire basées sur la fonction CPU
libre(a);
libre(b);
libre(c);
renvoie 0 ;

Makefile contenu ci-dessous :

INC=-I/usr/local/cuda/include
NVCC=/usr/local/cuda/bin/nvcc
NVCC_OPT=-std=c++11
tout:
$(NVCC) $(NVCC_OPT) exemple gpu.cpp -o gpu-exemple
faire le ménage:
-rm -f gpu-exemple

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

Fabriquer

Exécutez ensuite le programme :

./gpu-exemple

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 l'exemple gpu.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.

Comment télécharger et jouer à Civilization VI de Sid Meier sur Linux
Présentation du jeu Civilization 6 est une version moderne du concept classique introduit dans la série de jeux Age of Empires. L'idée était assez sim...
Comment installer et jouer à Doom sur Linux
Introduction à Doom La série Doom est née dans les années 90 après la sortie du Doom original. Ce fut un succès instantané et à partir de ce moment-là...
Vulkan pour les utilisateurs Linux
Avec chaque nouvelle génération de cartes graphiques, nous voyons les développeurs de jeux repousser les limites de la fidélité graphique et se rappro...