CUDA减少,大阵列的方法

时间:2016-01-27 14:22:19

标签: c++ cuda reduction

我有以下" Frankenstein"总和减少代码,部分来自common CUDA reduction slices,部分来自CUDA样本。

    __global__ void  reduce6(float *g_idata, float *g_odata, unsigned int n)
{
    extern __shared__ float sdata[];

    // perform first level of reduction,
    // reading from global memory, writing to shared memory
    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x*blockSize*2 + threadIdx.x;
    unsigned int gridSize = blockSize*2*gridDim.x;
    sdata[tid] = 0;
    float mySum = 0;   

    while (i < n) { 
        sdata[tid] += g_idata[i] + g_idata[i+MAXTREADS]; 
        i += gridSize; 
    }
   __syncthreads();


    // do reduction in shared mem
    if (tid < 256)
        sdata[tid] += sdata[tid + 256];
    __syncthreads();

    if (tid < 128)
        sdata[tid] +=  sdata[tid + 128];
     __syncthreads();

    if (tid <  64)
       sdata[tid] += sdata[tid +  64];
    __syncthreads();


#if (__CUDA_ARCH__ >= 300 )
    if ( tid < 32 )
    {
        // Fetch final intermediate sum from 2nd warp
        mySum = sdata[tid]+ sdata[tid + 32];
        // Reduce final warp using shuffle
        for (int offset = warpSize/2; offset > 0; offset /= 2) 
            mySum += __shfl_down(mySum, offset);
    }
    sdata[0]=mySum;
#else

    // fully unroll reduction within a single warp
    if (tid < 32) {
       sdata[tid] += sdata[tid + 32];
       sdata[tid] += sdata[tid + 16];
       sdata[tid] += sdata[tid + 8];
       sdata[tid] += sdata[tid + 4];
       sdata[tid] += sdata[tid + 2];
       sdata[tid] += sdata[tid + 1];
    }
#endif
    // write result for this block to global mem
    if (tid == 0) g_odata[blockIdx.x] = sdata[0];
  }

我将使用它来减少特斯拉k40 GPU上展开的大尺寸数组(例如512^3 = 134217728 = n)。

我对blockSize变量及其值有一些疑问。

从这里开始,我将尝试解释我对其工作原理的理解(正确或错误):

我选择blockSize越大,代码执行得越快,因为它将在整个循环中花费更少的时间,但它不会完成减少整个数组,但它将返回一个更小的数组dimBlock.x,对吧?如果我使用blockSize=1这个代码将在1中返回调用减少值,但它会非常慢,因为它几乎没有利用CUDA的强大功能。因此,我需要多次调用缩减内核,每次使用较小的blokSize,并将前一次调用的结果减少为reduce,直到达到最小点。

类似(pesudocode)

blocks=number; //where do we start? why?
while(not the min){

    dim3 dimBlock( blocks );
    dim3 dimGrid(n/dimBlock.x);
    int smemSize = dimBlock.x * sizeof(float);
    reduce6<<<dimGrid, dimBlock, smemSize>>>(in, out, n);

    in=out;

    n=dimGrid.x; 
    dimGrid.x=n/dimBlock.x; // is this right? Should I also change dimBlock?
}

我应该从哪个价值开始?我猜这是依赖GPU的。对于Tesla k40来说,它应该是哪些值(仅供我了解如何选择这些值)?

我的逻辑是否有些缺陷?如何?

1 个答案:

答案 0 :(得分:1)

有一个CUDA工具可以为您获得良好的网格和块大小:Cuda Occupancy API

响应“我选择更大的块大小,这个代码执行的速度越快” - 不一定,因为你想要的大小给出最大occupancy(活跃的比例)扭曲到可能的活动扭曲的总数。

有关其他信息,请参阅此答案How do I choose grid and block dimensions for CUDA kernels?

最后,对于支持Kelper或更高版本的Nvidia GPU,shuffle intrinsics可以更轻松,更快速地进行缩减。这是一篇关于如何使用shuffle内在函数的文章:Faster Parallel Reductions on Kepler

选择线程数的更新:

如果导致寄存器使用效率降低,您可能不想使用最大线程数。从占用链接:

为了计算占用率,每个线程使用的寄存器数量是关键因素之一。例如,具有计算能力1.1的设备每个多处理器具有8,192个32位寄存器,并且最多可以驻留768个并发线程(每个warp 24个warp x 32个线程)。这意味着在其中一个设备中,为了使多处理器具有100%的占用率,每个线程最多可以使用10个寄存器。但是,这种确定寄存器计数如何影响占用率的方法没有考虑寄存器分配粒度。例如,在计算能力1.1的设备上,每个线程使用12个寄存器的128个线程块的内核导致每个多处理器有5个活动128线程块的占用率为83%,而具有256个线程块的内核每个线程使用相同的12个寄存器导致占用率为66%,因为只有两个256线程块可以驻留在多处理器上。

所以我理解它的方式是,由于可以分配寄存器的方式,增加的线程数可能会限制性能。但是,情况并非总是如此,您需要自己进行计算(如上所述),以确定每个块的最佳线程数。