Chapter 4.3 : Performance



Lançons notre programme gray_scott_gpu_cuda avec time :

time ./program/gray_scott_gpu_cuda -n 1000 -r 1080 -c 1920 -e 34
asterics_getNbCudaDevice : Detected 1 CUDA Capable device(s)
simulateImage : found 1 device(s)
simulateImage : nbRow = 1080, nbCol = 1920
gray_scott_cuda : number of bunch 3
gray_scott_cuda : nbImagePerCall = 333
gray_scott_cuda : nbImageLastCall = 1
gray_scott_cuda : Grid size (36, 60, 1)
gray_scott_cuda : block size (30, 32, 1)
gray_scott_cuda : bunch 1/3
gray_scott_cuda : bunch 2/3
gray_scott_cuda : bunch 3/3
gray_scott_cuda : last bunch of 1 image(s) after 3 bunche(s) of 333 images each
Done

real 4m48,169s user 2m59,664s sys 1m43,696s


Il est possible de créer les images correspondantes :

mkdir OutputGrayScottCudaPng
time ./program/GrayScott2Pic/gray_scott2pic -i output.h5 -o OutputGrayScottCudaPng/
simulateImage : nbImage = 1000, nbRow = 1080, nbCol = 1920
[====================================================================================================|100%]
Done

real 20m46,122s user 20m29,471s sys 0m15,155s


L'ironie de cette histoire c'est que l'on met moins de temps à calculer les résultats qu'à produire les images.

Ensuite, nous pouvons assembler toutes les images pour faire une vidéo noteJ'ai utilisé blender mais vous pouvez prendre votre programme préféré. :



Cela nous permet de vérifier que les résultats sont justes.

Faisons un point concernant les performances obtenues :

  • 2h 43min 30s : implémentation C++17 naîve (avec le flag d'optimisation -O3)
  • 9min 49s : implémentation C++17 en fontions intrinsèques (sur AVX2)
  • 6min 41s : implémentation C++17 en fonction intrinsèques parallèle sur 8 threads (sur AVX2)
  • 4min 48s : implémentation Cuda 11.2 et C++11 sur un GPU M2200 (4 Go de DRam et 1024 coeurs)


Mis à part que la version Cuda est 34 fois plus rapide que la version naïve C++17, ce qui n'a aucun intéret sauf pour faire un test de performance fallacieux.

On peut remarquer que la version Cuda est 1.39 fois plus rapide que la meilleure version CPU. Ce résultat implique que le GPU est plus efficace que le CPU pour ce calcul, mais la version très optimisée sur CPU n'en reste pas moins efficace et compétitive.

Nous pouvons évaluer rapidement les capacités de calcul du GPU et du CPU.

D'un côté le GPU a 1024 coeurs à 1.04 GHz et le CPU a 8 coeurs, vectorisés 8 fois à 2.9 GHz. Donc :

nothing


Le GPU pourrait donc faire théoriquement 5.7 fois plus de calculs que le CPU mais l'implémentaion GPU n'est que 1.39 fois plus rapide que celle du CPU.

Bien entendu, les transferts de données sur le GPU réduisent ce facteur.

time ./program/gray_scott_gpu_cuda_stupid -n 1000 -r 1080 -c 1920 -e 34
asterics_getNbCudaDevice : Detected 1 CUDA Capable device(s)
simulateImage : found 1 device(s)
simulateImage : nbRow = 1080, nbCol = 1920
gray_scott_cuda_stupid : Grid size (36, 60, 1)
gray_scott_cuda_stupid : block size (30, 32, 1)
Done

real 6m34,715s user 6m9,708s sys 0m24,834s


Bon, si vous lancer nvidia-settings en même temps vous constaterez que le taux d'utilisation du GPU n'est quasiment jamais à 100\%. Donc rien d'étonnant à ce qu'il soit plus lent, et de toutes façon on l'avait annoncé dès le début. Cette version est 1.37 fois plus lente que notre permière version CUDA. Cela montre bien qu'il faut être particulièrement attentif au transfert des données lorsque l'on utilise un GPU.