共享内存,分支性能和寄存器计数

时间:2014-11-18 16:13:20

标签: cuda

在尝试CUDA shuffle指令时,我遇到了一些特殊的性能行为。下面的测试内核基于图像处理算法,该算法将依赖于输入的值添加到边rad的正方形内的所有相邻像素。每个块的输出都添加到共享内存中。如果每个warp只有一个线程将其结果添加到共享内存,则性能很差(选项1),而另一方面,如果所有线程都添加到共享内存(一个线程添加所需的值,其余只添加0),执行时间下降2-3倍(选项2)。

#include <iostream>
#include "cuda_runtime.h"

#define warpSz 32
#define tileY 32
#define rad 32

__global__ void test(float *out, int pitch)
{
    // Set shared mem to 0
    __shared__ float tile[(warpSz + 2*rad) * (tileY + 2*rad)];
    for (int i = threadIdx.y*blockDim.x+threadIdx.x; i<(tileY+2*rad)*(warpSz+2*rad); i+=blockDim.x*blockDim.y) {
        tile[i] = 0.0f;
    }
    __syncthreads();

    for (int row=threadIdx.y; row<tileY; row += blockDim.y) {
        // Loop over pixels in neighbourhood
        for (int i=0; i<2*rad+1; ++i) {
            float res = 0.0f;
            int rowStartIdx = (row+i)*(warpSz+2*rad);
            for (int j=0; j<2*rad+1; ++j) {
                res += float(threadIdx.x+row); // Substitute for real calculation

                // Option 1: one thread writes to shared mem
                if (threadIdx.x == 0) {
                    tile[rowStartIdx + j] += res;
                    res = 0.0f;
                }

                //// Option 2: all threads write to shared mem
                //float tmp = 0.0f;
                //if (threadIdx.x == 0) {
                //  tmp = res;
                //  res = 0.0f;
                //}
                //tile[rowStartIdx + threadIdx.x+j] += tmp;

                res = __shfl(res, (threadIdx.x+1) % warpSz);
            }
            res += float(threadIdx.x+row);
            tile[rowStartIdx + threadIdx.x+2*rad] += res;
            __syncthreads();
        }
    }

    // Add result back to global mem
    for (int row=threadIdx.y; row<tileY+2*rad; row+=blockDim.y) {
        for (int col=threadIdx.x; col<warpSz+2*rad; col+=warpSz) {
            int idx = (blockIdx.y*tileY + row)*pitch + blockIdx.x*warpSz + col;
            atomicAdd(out+idx, tile[row*(warpSz+2*rad) + col]);
        }
    }
}

int main(void)
{
    int2 dim = make_int2(512, 512);
    int pitchOut = (((dim.x+2*rad)+warpSz-1) / warpSz) * warpSz;
    int sizeOut = pitchOut*(dim.y+2*rad);
    dim3 gridDim((dim.x+warpSz-1)/warpSz, (dim.y+tileY-1)/tileY, 1);
    float *devOut;
    cudaMalloc((void**)&devOut, sizeOut*sizeof(float));
    cudaEvent_t start, stop;
    float elapsedTime;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);
    cudaFree(0);
    cudaEventRecord(start, 0);
    test<<<gridDim, dim3(warpSz, 8)>>>(devOut, pitchOut);
    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    cudaEventElapsedTime(&elapsedTime, start, stop);
    cudaFree(devOut);
    cudaDeviceReset();
    std::cout << "Elapsed time: " << elapsedTime << " ms.\n";
    std::cin.ignore();
}

这是预期的行为/任何人都可以解释为什么会发生这种情况吗?

我注意到的一件事是选项1仅使用15个寄存器,而选项2使用37,这对我来说似乎有很大的不同。

另一个是最内层循环中的if语句在选项1的PTX代码中转换为显式bra指令,而对于选项2,它转换为两条selp指令。可能是明确的分支落后于慢慢下降2-3倍,与this question中怀疑的相似吗?

为什么我不愿意使用选项2有两个原因。首先,在分析原始应用程序时,它似乎受到共享内存带宽的限制,这表明有可能通过减少访问它的线程来提高性能。其次,除非我们使用volatile关键字,否则可以将对共享内存的写入优化为寄存器。由于我们只对最后一个线程访问每个内存位置(threadIdx.x == 0)的贡献以及所有其他内容添加0感兴趣,所以只要暂时位于寄存器中的所有更改都保证为按照发布的顺序写回共享内存。这是这种情况吗? (到目前为止,两种选择都产生了完全相同的结果。)

非常感谢任何想法或想法!

PS。我编译计算能力3.0。 (但是,shuffle指令不是演示行为所必需的,可以被注释掉。)

0 个答案:

没有答案