CUDA:重载共享内存以实现具有多个数组的减少方法

时间:2017-12-19 22:18:43

标签: c++ cuda reduction bank-conflict gpu-shared-memory

我有5个大尺寸阵列A(N * 5),B(N * 5),C(N * 5),D(N * 5),E(N * 2) 数字5和2表示这些变量在不同平面/轴上的分量。 这就是为什么我以这种方式构造数组的原因所以我可以在编写代码时可视化数据。 N~200 ^ 3~8e06个节点

例如:这是我的内核最简单的形式,我正在对全局内存进行所有计算。

#define N 200*200*200
__global__ void kernel(doube *A, double *B, double *C, 
            double *D, double *E, double *res1, double *res2, 
            double *res3, double *res4 )
    {
       int a, idx=threadIdx.x + blockIdx.x * blockDim.x;
        if(idx>=N) {return;}
        res1[idx]=0.; res2[idx]=0.; 
        res3[idx]=0.; res4[idx]=0.

        for (a=0; a<5; a++)
        {
            res1[idx] += A[idx*5+a]*B[idx*5+a]+C[idx*5+a] ;
            res2[idx] += D[idx*5+a]*C[idx*5+a]+E[idx*2+0] ;
            res3[idx] += E[idx*2+0]*D[idx*5+a]-C[idx*5+a] ;
            res4[idx] += C[idx*5+a]*E[idx*2+1]-D[idx*5+a] ;
        }

    }

我知道&#34;因为&#34;循环可以消除,但我把它留在这里,因为它很方便查看代码。 这很有效但很明显,对于特斯拉K40卡来说效率极低且速度极慢,即使在去除&#34; for&#34;环。在&#34; for&#34;中显示的算术结构循环只是为了给出一个想法,实际的计算时间更长,并且使用res1,res2进行复杂处理......也可以进行混合。

我实施了以下有限的改进,但是 我希望通过超载共享内存来进一步改进它。

    #define THREADS_PER_BLOCK 256
    __global__ void kernel_shared(doube *A, double *B, double *C, 
               double *D, double *E, double *res1, double *res2, 
               double *res3, double *res4  )
    {
       int a, idx=threadIdx.x + blockIdx.x * blockDim.x;
       int ix = threadIdx.x;
       __shared__ double A_sh[5*THREADS_PER_BLOCK];
       __shared__ double B_sh[5*THREADS_PER_BLOCK];
       __shared__ double C_sh[5*THREADS_PER_BLOCK];
       __shared__ double D_sh[5*THREADS_PER_BLOCK];
       __shared__ double E_sh[2*THREADS_PER_BLOCK];

       //Ofcourse this will not work for all arrays in shared memory; 
        so I am allowed  to put any 2 or 3 variables (As & Bs) of  
         my choice in shared and leave rest in the global memory. 

       for(int a=0; a<5; a++)
     {
        A_sh[ix*5 + a] = A[idx*5 + a] ;
        B_sh[ix*5 + a] = B[idx*5 + a] ;
     }
            __syncthreads();



    if(idx>=N) {return;}
        res1[idx]=0.; res2[idx]=0.; 
        res3[idx]=0.; res4[idx]=0.
    for (a=0; a<5; a++)
    {
        res1[idx] += A_sh[ix*5+a]*B_sh[ix*5+a]+C[idx*5+a];
        res2[idx] += B_sh[ix*5+a]*C[idx*5+a]+E[idx*2+0]  ;
        res3[idx] += E[idx*2+0]*D[idx*5+a]-C[idx*5+a]    ;
        res4[idx] += B_sh[ix*5+a]*E[idx*2+1]-D[idx*5+a]  ;
    }

}

这有点帮助,但我想实现其中一个减少 接近(没有银行冲突)以提高性能,我可以把所有 我的共享变量(可能是平铺方法),然后做计算部分。 我在CUDA_Sample文件夹中看到了缩减示例,但该示例 仅用于共享一个向量的总和,而不需要从共享内存中的多个数组中涉及任何复杂的算术。我将不胜感激任何帮助或建议,以改进我现有的kernel_shared方法,以包括减少方法。

1 个答案:

答案 0 :(得分:1)

1。你需要的不是共享内存

检查你的初始内核,我们注意到对于a的每个值,你在计算四个增量时最多使用12个值(可能小于12,我不算数)究竟)。这一切都非常适合您的寄存器文件 - 即使是双值:12 * sizeof(double),加上4 * sizeof(double)的中间结果,每个线程有32个4字节寄存器。即使每个块有1024个线程,也超出限制。

现在,内核运行缓慢的原因主要是

2。次优内存访问模式

在任何CUDA编程的演示中,您都可以阅读这些内容;我只是简单地说,不是每个线程自己处理几个连续的数组元素,而是应该在warp的通道之间交错,或者更好的是块的线程。因此代替线程全局索引idx处理

5 * idx
5 * idx + 1
...
5 * idx + 4

让它处理

5 * blockDim.x * blockIdx.x + threadIdx.x
5 * blockDim.x * blockIdx.x + threadIdx.x + blockDim.x
...
5 * blockDim.x * blockIdx.x + threadIdx.x + 4 * blockDim.x

这样无论何时线程读或写,它们的读写都会合并。在你的情况下,这可能会有点棘手,因为你的一些访问有一个稍微不同的模式,但你明白了。

3。过度添加到全局内存中的位置

此问题更适用于您的情况。你知道,你真的不需要在每个之后更改全局的resN[idx]值,并且你当然不关心读取那里的值。 '即将写。正如你的内核所代表的那样,单个线程为resN[idx]计算一个新值 - 所以它可以在寄存器中添加东西,并在完成后写入resN[idx](甚至不查看其地址)。

如果您按照我在第1点中的建议更改了内存访问模式,那么在第2点中实现建议会变得更加棘手,因为您需要在同一个warp中添加来自多个通道的值,并且可能会生成确保不要使用与单个计算相关的读数跨越warp边界。要了解如何执行此操作,建议您查看this presentation有关基于随机播放的缩减。