Surprise avec Cuda
- Mardi 4 janvier 2011
- Ecrire commentaire
Ces derniers temps je fais pas mal de Cuda.
J’ai écris un petit mandelbrot et en faisant quelques tests j’ai découvert quelques surprises…
Voici le code original de la fonction de calcul:
__global__ void mandelbrot(int* A, const int N, const int largeur, const int hauteur){ int idx = blockDim.x * blockIdx.x + threadIdx.x; int y = idx / hauteur; int x = idx - (y * largeur); if (y < hauteur && x < largeur) { int cpt = 0; float x1 = 0.; float y1 = 0.; float x2 = 0.; float y2 = 0.; float a = 4. * x / largeur - 2.; float b = 4. * y / hauteur - 2.; float val = x1* x1 + y1 * y1; while (cpt < N && val <= 4.) { cpt ++; x2 = x1* x1 - y1 * y1 + a; y2 = 2. * x1 * y1 + b; x1 = x2; y1 = y2; val = x1* x1 + y1 * y1; } A[y*hauteur+x] = cpt; }
Ce calcul a lieu pour chaque pixel de l’image à générer. Cuda me permet de produire un grand nombre de threads (1 par pixel) qui vont s’exécuter en parallèle sur la carte graphique (plus ou moins nombreux à la fois et plus ou moins vite en fonction du GPU Nvidia utilisé).
Ce petit programme me permet de générer mon image beaucoup plus rapidement qu’en sequentiel (même sur un petit GPU).
Pour mes travaux futurs je vais avoir besoin de fonction Cuda plus complexes et surtout qui appellent d’autres fonctions. Pour voir l’impact sur les performances des appels de fonctions j’ai modifié le code de mandelbrot ainsi:
__global__ void mandelbrot2(int* A, const int N, const int largeur, const int hauteur){ int idx = addi(multi(blockDim.x, blockIdx.x), threadIdx.x); int y = divi(idx, hauteur); int x = subi(idx, multf(y, largeur)); if (andi(lti(y, hauteur), lti(x, largeur))) { int cpt = 0; float x1 = 0.; float y1 = 0.; float x2 = 0.; float y2 = 0.; float a = subf(multf(4., divf( x, largeur)) , 2.); float b = subf(multf(4., divf(y, hauteur)), 2.); float val = addf(multf(x1, x1), multf(y1, y1)); while (andi(lti(cpt, N), leqf(val, 4.))) { cpt = addi(cpt, 1); x2 = addf(subf(multf(x1, x1), multf(y1, y1)), a); y2 = addf(multf(multf(2., x1), y1), b); x1 = x2; y1 = y2; val = addf(multf(x1, x1) , multf(y1, y1)); } A[addi(multi(y,hauteur),x)] = cpt; } }
avec
__device__ float addf(float a, float b) { return a + b; } __device__ int addi(int a, int b) { return a + b; } __device__ float subf(float a, float b) { return a - b; } __device__ int subi(int a, int b) { return a - b; } __device__ float multf(float a, float b) { return a * b; } __device__ int multi(int a, int b) { return a * b; } __device__ float divf(float a, float b) { return a / b; } __device__ int divi(int a, int b) { return a / b; } __device__ int lti (int a, int b) { return a < b ; } __device__ int ltf (float a, float b) { return a < b ; } __device__ int leqi (int a, int b) { return a <= b ; } __device__ int leqf (float a, float b) { return a <= b ; } __device__ int andi(int a, int b) { return a && b; }
Le mot clef __device__ indique que la fonction doit être compilé pour le GPU.
Comme vous le voyez rien de bien méchant dans tout cela.
La surprise est venue lors des benchs:
| Mandelbrot | Mandelbrot2 | |
| nForce 750a | 7.51s | 6.17s |
| GeForce GTX 460 | 0.9s | 0.36s |
| Tesla C2070 | 0.28s | 0.23s |
Pour info : en séquentiel, sur un core de Phenom II X6 @3.4GHz, le programme met 41.49s à générer l’image.
On constate donc que l’utilisation des fonctions dans mon petit calcul ne fait pas perdre en performance mais au contraire qu’il permet de gagner un peu. Evidemment ce n’est pas du aux appels de fonction en eux même ni aux fonctions en question mais à des questions d’optimisation lors de la compilation.
Étonnant non?
