2011-10-25 3 views
0

Я пытаюсь распараллелить функцию, которая принимает в качестве входных данных три массива (x, y и prb) и один скаляр, и выводит три массива (P1, Pt1 и Px).Как эффективно распараллелить этот тройной цикл?

оригинальный с кодом здесь (останец и Е несущественны):

#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; 
} 

Вот моя попытка распараллеливания его:

#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; 

} 

Однако, к моему ужасу, он работает гораздо , намного медленнее, чем исходная версия. Как заставить его работать быстрее? Пожалуйста, подробно объясните, так как я очень новичок в CUDA и параллельном программировании и не имею опыта в алгоритмах.

Обратите внимание, что версия c имеет порядок столбцов, а версия CUDA имеет ряд строк. Я сделал несколько тестов, чтобы убедиться, что результат правильный. Это просто очень медленно и занимает много памяти.

Любая помощь очень ценится!

EDIT: Дополнительная информация: N и М порядка нескольких тысяч (скажем, 300-3000) и D всегда 3. Версия CUDA ожидает массивы быть устройства памяти для переменных с префиксом, кроме час_.

+0

Что вычисляет этот код? –

+0

Можете ли вы опубликовать некоторые ориентировочные значения M, N и D? – talonmies

+0

M и N стоят порядка нескольких тысяч, а D равно 3. Вычисление, которое этот код реализует, используется для обработки облаков точек, но я не слишком уверен, что такое название этого метода (или если оно даже имеет один). – Daniel

ответ

1

Перед попыткой любой оптимизации, специфичной для CUDA, просмотрите свой код, чтобы узнать, где тратится время.

Попробуйте организовать чтение или запись массива так, чтобы каждый поток CUDA использовал шаблон с чередованием. Например, в настоящее время у вас есть

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], 

Так нить 1 будет читать из y[0],y[1],y[2] и т.д. Вместо этого, изменить свои данные, чтобы поток 1 считывает из y[0],y[M],y[2*M] и поток 2 считывает из y[1],y[M+1],y[2*M+1] и т.д. Вы должны следовать этому шаблону доступа к другой массивы.

Кроме того, вы можете рассмотреть вопрос о том, можете ли вы избежать использования __syncthreads(). Я не совсем понимаю, почему это необходимо в этом алгоритме, возможно, стоит удалить его, чтобы убедиться, что он повышает производительность (даже если он производит неверные результаты).

+0

Спасибо за предложение. Есть ли способ объединить доступ к памяти, не изменяя все мои данные от формата строки до основного? Есть месяцы работы, введенные в код, что в решающей степени зависит от этого, и я не думаю, что могу изменить все мои данные. Я удалил '__syncthreads()', и он отлично работает, хотя я не могу обнаружить никакой разницы в скорости. Кроме того, Nvidia Compute Visual Profiler говорит, что второе ядро ​​d_sumP занимает в 4-5 раз больше времени, чем первое ядро, d_computeP. – Daniel

+1

Я бы настоятельно советовал расследовать перенос массивов, которые вы используете в ядре 'd_sumP'. Вы можете сделать это программно, используя ядра «transpose», доступные в SDK NVIDIA, вы вполне можете обнаружить, что стоимость вычисления транспонирования перевешивается приростом производительности памяти. При этом можно добиться объединения памяти с использованием общей памяти, если вы не можете достичь этого каким-либо другим способом. Вы должны быть в состоянии найти информацию об этой технике в Интернете. –

+0

Мне удалось объединить некоторые обращения к памяти! в 'd_computeP' я заменил« Px [3 * (N * m + n) +0] = x1 * Pm * prbn; 'с' Px [m + M * (n + N * 0)] = x1 * Pm * prbn; 'и в' d_sumP' я заменил 'Pxm1 + = Pxtimessp [3 * (N * m + n) +0] * spn; 'с' Pxm1 + = Pxtimessp [m + M * (n + N * 0) ] * spn; '(и, конечно, для двух других, поскольку эти утверждения входят в тройку). Я также сделал то же самое для P1. Теперь код работает как минимум на 15% быстрее! Я рассмотрю, как объединить остальную часть доступа к памяти позже сегодня. – Daniel

0

Ключом к хорошим показателям CUDA является почти всегда максимально приближенное к оптимальному доступу к памяти. Ваш шаблон доступа к памяти очень похож на матричное умножение. Я бы начал с хорошей реализации умножения матрицы CUDA, не задумываясь о том, почему она реализована так, как она есть, а затем измените ее в соответствии с вашими потребностями.