2011-10-25 3 views
0

J'essaie de paralléliser une fonction qui prend en entrée trois tableaux (x, y et prb) et un scalaire, et sort trois tableaux (P1, Pt1 et Px).Comment paralléliser cette triple boucle de manière efficace?

Le code original c est ici (les valeurs aberrantes et E sont sans conséquence):

#include <stdio.h> 
#include <math.h> 
#include <stdlib.h> 
#define max(A, B) ((A) > (B) ? (A) : (B)) 
#define min(A, B) ((A) < (B) ? (A) : (B)) 

void cpd_comp(
     double* x, 
     double* y, 
     double* prb, 
     double* sigma2, 
     double* outlier, 
     double* P1, 
     double* Pt1, 
     double* Px, 
     double* E, 
     int N, 
     int M, 
     int D 
     ) 

{ 
    int  n, m, d; 
    double ksig, diff, razn, outlier_tmp, sp; 
    double *P, *temp_x; 

    P = (double*) calloc(M, sizeof(double)); 
    temp_x = (double*) calloc(D, sizeof(double)); 

    ksig = -2.0 * *sigma2; 


    for (n=0; n < N; n++) { 

     sp=0; 
     for (m=0; m < M; m++) { 
      razn=0; 
      for (d=0; d < D; d++) { 
      diff=*(x+n+d*N)-*(y+m+d*M); diff=diff*diff; 
      razn+=diff; 
      } 

      *(P+m)=exp(razn/ksig) ; 
      sp+=*(P+m); 
     } 


     *(Pt1+n)=*(prb+n); 
     for (d=0; d < D; d++) { 
     *(temp_x+d)=*(x+n+d*N)/ sp; 
     } 

     for (m=0; m < M; m++) { 
      *(P1+m)+=((*(P+m)/ sp) **(prb+n)); 

      for (d=0; d < D; d++) { 
      *(Px+m+d*M)+= (*(temp_x+d)**(P+m)**(prb+n)); 
      } 

     } 

    *E += -log(sp);  
    } 
    *E +=D*N*log(*sigma2)/2; 


    free((void*)P); 
    free((void*)temp_x); 

    return; 
} 

Voici ma tentative de parallélisation il:

#include <cuda.h> 
#include <cuda_runtime.h> 
#include <device_launch_parameters.h> 
#include <thrust/device_ptr.h> 
#include <thrust/reduce.h> 

/*headers*/ 
void cpd_comp(
    float * x,  //Points to register  [N*D] 
    float * y,  //Points to be registered [M*D] 
    float * prb,  //Vector of probabilities [N] 
    float * sigma2, //Square of sigma 
    float ** P1,  //P1, output, [M] 
    float ** Pt1,  //Pt1, output, [N] 
    float ** Px,  //Px, output, [M*3] 
    int N,   //Number of points, i.e. rows, in x 
    int M    //Number of points, i.e. rows, in 
    ); 

__global__ void d_computeP(
    float * P, 
    float * P1, 
    float * Px, 
    float * ProbabilityMatrix, 
    float * x, 
    float * y, 
    float * prb, 
    float ksig, 
    const int N, 
    const int M); 

__global__ void d_sumP(
    float * sp, 
    float * P1timessp, 
    float * Pxtimessp, 
    float * P1, 
    float * Px, 
    const int N, 
    const int M); 

/*implementations*/ 

void cpd_comp(
    float * x,  //Points to register  [N*D] 
    float * y,  //Points to be registered [M*D] 
    float * prb,  //Vector of probabilities [N] 
    float * sigma2, //Scalar 
    float ** P1,  //P1, output, [M] 
    float ** Pt1,  //Pt1, output, [N] 
    float ** Px,  //Px, output, [M*3] 
    int N,   //Number of points, i.e. rows, in x 
    int M    //Number of points, i.e. rows, in y 
    ){ 
    //X is generatedPointPos 
    //Y is points 

    float 
     *P, 
     *P1timessp, 
     *Pxtimessp, 
     ksig = -2.0 * (*sigma2), 
     *h_sumofP = new float[N], //sum of P, on host 
     *d_sumofP;    //sum of P, on device 

    cudaMalloc((void**)&P,  sizeof(float)*M*N); 
    cudaMalloc((void**)&P1timessp,sizeof(float)*M*N); 
    cudaMalloc((void**)&Pxtimessp,sizeof(float)*M*N*3); 
    cudaMalloc((void**)&d_sumofP, sizeof(float)*N); 

    cudaMalloc((void**)P1,  sizeof(float)*M); 
    cudaMalloc((void**)Px,  sizeof(float)*M*3); 
    cudaMalloc((void**)Pt1,  sizeof(float)*N); 

    d_computeP<<<dim3(N,M/1024+1),M>1024?1024:M>>>(P,P1timessp,Pxtimessp,NULL,x,y,prb,ksig,N,M); 

    for(int n=0; n<N; n++){ 
     thrust::device_ptr<float>dev_ptr(P); 
     h_sumofP[n] = thrust::reduce(dev_ptr+M*n,dev_ptr+M*(n+1),0.0f,thrust::plus<float>()); 
    } 

    cudaMemcpy(d_sumofP,h_sumofP,sizeof(float)*N,cudaMemcpyHostToDevice); 

    d_sumP<<<M/1024+1,M>1024?1024:M>>>(d_sumofP,P1timessp,Pxtimessp,*P1,*Px,N,M); 

    cudaMemcpy(*Pt1,prb,sizeof(float)*N,cudaMemcpyDeviceToDevice); 

    cudaFree(P); 
    cudaFree(P1timessp); 
    cudaFree(Pxtimessp); 
    cudaFree(d_sumofP); 
    delete[]h_sumofP; 
} 

/*kernels*/ 

__global__ void d_computeP(
    float * P, 
    float * P1, 
    float * Px, 
    float * ProbabilityMatrix, 
    float * x, 
    float * y, 
    float * prb, 
    float ksig, 
    const int N, 
    const int M){ 
    //thread configuration: <<<dim3(N,M/1024+1),1024>>> 
    int m = threadIdx.x+blockIdx.y*blockDim.x; 
    int n = blockIdx.x; 
    if(m>=M || n>=N) return; 

    float 
     x1 = x[3*n], 
     x2 = x[3*n+1], 
     x3 = x[3*n+2], 
     diff1 = x1 - y[3*m], 
     diff2 = x2 - y[3*m+1], 
     diff3 = x3 - y[3*m+2], 
     razn = diff1*diff1+diff2*diff2+diff3*diff3, 

     Pm = __expf(razn/ksig), //fast exponentiation 
     prbn = prb[n]; 

    P[M*n+m] = Pm; 

    __syncthreads(); 

    P1[N*m+n] = Pm*prbn; 
    Px[3*(N*m+n)+0] = x1*Pm*prbn; 
    Px[3*(N*m+n)+1] = x2*Pm*prbn; 
    Px[3*(N*m+n)+2] = x3*Pm*prbn; 
} 

__global__ void d_sumP(
    float * sp, 
    float * P1timessp, 
    float * Pxtimessp, 
    float * P1, 
    float * Px, 
    const int N, 
    const int M){ 
    //computes P1 and Px 
    //thread configuration: <<<M/1024+1,1024>>> 
    int m = threadIdx.x+blockIdx.x*blockDim.x; 
    if(m>=M) return; 
    float 
     P1m = 0, 
     Pxm1 = 0, 
     Pxm2 = 0, 
     Pxm3 = 0; 
    for(int n=0; n<N; n++){ 
     float spn = 1/sp[n]; 
     P1m += P1timessp[N*m+n]*spn; 
     Pxm1 += Pxtimessp[3*(N*m+n)+0]*spn; 
     Pxm2 += Pxtimessp[3*(N*m+n)+1]*spn; 
     Pxm3 += Pxtimessp[3*(N*m+n)+2]*spn; 
    } 

    P1[m] = P1m; 
    Px[3*m+0] = Pxm1; 
    Px[3*m+1] = Pxm2; 
    Px[3*m+2] = Pxm3; 

} 

Cependant, à ma grande horreur, il fonctionne bien , beaucoup plus lent que la version originale. Comment puis-je le faire fonctionner plus vite? S'il vous plaît expliquer les choses à fond puisque je suis très nouveau à CUDA et la programmation parallèle et n'ai aucune expérience dans les algorithmes.

Notez que la version c dispose d'une commande de colonnes majeures et que la version CUDA a une ligne majeure. J'ai fait plusieurs tests pour m'assurer que le résultat est correct. C'est juste extrêmement lent et prend beaucoup de mémoire.

Toute aide est grandement appréciée!

EDIT: Plus d'informations: N et M sont de l'ordre de quelques milliers (disons, 300-3000) et D est toujours 3. La version CUDA attend des tableaux pour être mémoire de l'appareil, à l'exception des variables préfixées h_.

+0

Quel calcul est implémenté par ce code? –

+0

Pouvez-vous poster quelques valeurs indicatives de M, N et D? – talonmies

+0

M et N sont de l'ordre de quelques milliers, et D est 3. Le calcul que ce code implémente est utilisé pour traiter les nuages ​​de points, mais je ne suis pas très sûr de savoir quel est le nom de cette technique (ou même en a un). – Daniel

Répondre

1

Avant d'essayer des optimisations spécifiques à CUDA, créez un profil pour voir où l'heure est passée. Essayez et organisez vos lectures/écritures de tableau de sorte que chaque thread CUDA utilise un modèle d'accès strié. Par exemple, vous avez actuellement

int m = threadIdx.x+blockIdx.y*blockDim.x; 
int n = blockIdx.x; 
if(m>=M || n>=N) return; 

diff1 = x1 - y[3*m], 
diff2 = x2 - y[3*m+1], 
diff3 = x3 - y[3*m+2], 

Alors fil 1 sera lu à partir y[0],y[1],y[2] etc. Au lieu de cela, réorganiser vos données afin que le fil 1 lit de 2 y[0],y[M],y[2*M] et fils lit de y[1],y[M+1],y[2*M+1] etc. Vous devez suivre ce modèle d'accès pour d'autres tableaux.

En outre, vous pouvez décider si vous pouvez éviter l'utilisation de __syncthreads(). Je ne suis pas tout à fait pourquoi il est nécessaire dans cet algorithme, il pourrait être utile de l'enlever pour voir si elle améliore les performances (même si elle produit des résultats incorrects).

+0

Merci pour la suggestion. Existe-t-il un moyen de fusionner les accès mémoire sans changer toutes mes données du format row-major au format column-major? Il y a des mois de travail mis dans le code qui en dépend crucialement et je ne pense pas pouvoir réorganiser toutes mes données. J'ai enlevé le '__syncthreads()' et ça marche bien, bien que je ne puisse pas détecter de différence de vitesse. En outre, le profileur visuel Nvidia Compute dit que le deuxième noyau d_sumP prend 4-5 fois plus de temps que le premier noyau, d_computeP. – Daniel

+1

Je vous conseillerais vivement d'étudier la transposition des tableaux que vous utilisez dans le noyau 'd_sumP'. Vous pouvez le faire par programmation en utilisant les noyaux 'transpose' disponibles dans le NVIDIA SDK, vous pouvez bien constater que le coût du calcul de la transposition est compensé par le gain de performance de la mémoire. Cela dit, il est possible de réaliser une fusion de mémoire en utilisant la mémoire partagée si vous ne pouvez pas le faire autrement. Vous devriez être capable de trouver des informations sur cette technique sur le web. –

+0

J'ai réussi à fusionner certains des accès mémoire! dans 'd_computeP' j'ai remplacé' Px [3 * (N * m + n) +0] = x1 * Pm * prbn; 'avec' Px [m + M * (n + N * 0)] = x1 * Pm * prbn; 'et dans' d_sumP' j'ai remplacé 'Pxm1 + = Pxtimessp [3 * (N * m + n) +0] * spn;' avec 'Pxm1 + = Pxtimessp [m + M * (n + N * 0) ] * spn; '(et bien sûr pour les deux autres, puisque ces déclarations viennent par trois). J'ai aussi fait la même chose pour P1. Le code fonctionne maintenant au moins 15% plus vite! Je chercherai à fusionner le reste des accès mémoire plus tard aujourd'hui. – Daniel

0

La clé de bonnes performances CUDA est presque toujours de rendre aussi proche que possible de l'accès mémoire optimal. Votre modèle d'accès à la mémoire ressemble beaucoup à la multiplication matricielle. Je commencerais par une bonne implémentation de la multiplication matricielle CUDA, en étant sûr de comprendre pourquoi elle est implémentée comme elle est, puis en la modifiant pour répondre à vos besoins.

Questions connexes