CUDA高效师?

时间:2012-10-04 14:13:56

标签: cuda gpu gpgpu nvidia

我想知道是否有任何机会分割数组元素的有效方法。我运行的矩阵值为10000x10000,与其他内核相比,它需要相当长的时间。分部是昂贵的操作,我看不出如何改进它。

__global__ void division(int N, float* A, int* B){

  int row = blockIdx.x * blockDim.x + threadIdx.x;
  int col = blockIdx.y * blockDim.y + threadIdx.y;

  if((row < N) && (col <= row) ){
    if( B[row*N+col] >0 )
      A[row*N+col] /= (float)B[row*N+col];
  }

}
使用

启动

内核

  int N = 10000;
  int threads = 32
  int blocks = (N+threads-1)/threads
  dim3 t(threads,threads);
  dim3 b(blocks, blocks);
  division<<< b, t >>>(N, A, B);
  cudaThreadSynchronize();

选项B:

__global__ void division(int N, float* A, int* B){
  int k =  blockIdx.x * blockDim.x + threadIdx.x;
  int kmax = N*(N+1)/2 
  int i,j;
  if(k< kmax){
    row = (int)(sqrt(0.25+2.0*k)-0.5); 
    col = k - (row*(row+1))>>1;
    if( B[row*N+col] >0 )
      A[row*N+col] /= (float)B[row*N+col];
  }
}

启动

  int threads =192;
  int totalThreadsNeeded = (N*(N+1)/2;
  int blocks = ( threads + (totalThreadsNeeded)-1 )/threads;
  division<<<blocks, threads >>>(N, A, B);

为什么即使threadIds是正确的,选项B也会给出错误的结果?这里缺少什么?

3 个答案:

答案 0 :(得分:3)

您的基本问题是您正在启动一个不可思议的巨大网格(超过1亿个线程用于您的10000x10000阵列示例),然后由于内核中访问模式的三角形特性,这些线程中的一半完全没有做任何事情生产力。因此,没有特别好的理由浪费了大量的GPU周期。此外,您正在使用的访问模式不允许合并内存访问,这将进一步降低实际执行有用工作的线程的性能。

如果我正确理解你的问题,内核只对正方形数组的下三角形进行逐元素划分。如果是这种情况,可以使用以下方式同样完成:

__global__ 
void division(int N, float* A, int* B)
{
    for(int row=blockIdx.x; row<N; row+=gridDim.x) {
        for(int col=threadIdx.x; col<=row; col+=blockDim.x) {
            int val = max(1,B[row*N+col]);
            A[row*N+col] /= (float)val;
        }
    }
}

[免责声明:用浏览器编写,从未编译,从未测试过,使用风险自负]

这里,使用一维网格,每个块一次计算一行。块中的线程沿着行移动,因此内存访问被合并。在评论中你提到你的GPU是特斯拉C2050。该设备仅需要112个192个线程的块,每个块完全“填充”14个SM中的每一个,每个块具有8个块的完整补充和每个SM的最大并发线程数。所以启动参数可能是这样的:

int N = 10000;
int threads = 192;
int blocks = min(8*14, N);
division<<<blocks, threads>>>(N, A, B);

我希望这比你当前的方法跑得快得多。如果数值精度不是那么重要,你可以通过用近似的倒数内禀和浮点乘以替换除法来实现进一步的加速。

答案 1 :(得分:2)

因为线程是在32组(称为warp)中执行的,所以如果两个if条件对于其中一个线程true条件都是false,那么您需要为warp中的所有32个线程支付除法。如果许多线程的条件为(a / b == a * 1 / b),请查看是否可以在单独的内核中过滤掉不需要除法的值。

浮点转换的int本身可能很慢。如果是这样,您可以直接在前面的步骤中生成浮点数,并将B作为浮点数组传递。

您可以在前面的步骤中生成反转数字,您可以在其中生成B数组。如果是这样,你可以在这个内核中使用乘法而不是除法。 __fdividef(x, y)

根据您的算法,也许您可​​以通过较低的精度划分来逃避。您可以尝试使用内在的-prec-div=false。还有一个编译器标志{{1}}。

答案 2 :(得分:2)

首先要看的是合并内存访问。这里没有合并模式的原因,只需交换行和列以避免浪费大量内存带宽:

int col = blockIdx.x * blockDim.x + threadIdx.x;
int row = blockIdx.y * blockDim.y + threadIdx.y;
...
A[row*N+col] ...

即使这是在计算能力2.0或更高版本上运行,缓存也不足以弥补这种次优模式。