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

Cuda 共享内存、分支性能和寄存器计数,cuda,Cuda,在尝试CUDAshuffle指令时,我遇到了一些特殊的性能行为。下面的测试内核基于一种图像处理算法,该算法将依赖于输入的值添加到一个正方形rad内的所有相邻像素。每个块的输出都添加到共享内存中。如果每个扭曲只有一个线程将其结果添加到共享内存中,则性能较差(选项1),而另一方面,如果所有线程都添加到共享内存中(一个线程添加所需的值,其余线程仅添加0),则执行时间将下降2-3倍(选项2) #包括 #包括“cuda_runtime.h” #定义warpSz 32 #定义tileY 32 #定义rad

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

#包括
#包括“cuda_runtime.h”
#定义warpSz 32
#定义tileY 32
#定义rad 32
__全局无效测试(浮点*输出,整数变桨)
{
//将共享内存设置为0
__共享浮动瓷砖[(翘曲+2*rad)*(瓷砖+2*rad)];

对于(int i=threadIdx.y*blockDim.x+threadIdx.x;i支持输入Young Bae。我知道,如果线程对同一位置执行写指令,行为是未定义的。由于写缓存,同样的情况也是如此,如果两个线程,即使它们在同一个扭曲中,也在不同的指令中读修改写同一位置。然而,在这里e前31个线程被读取,然后,可能由于缓存而延迟,写回相同的结果。如果缓存的写操作的发布顺序保持不变,那么这是有效的,否则它是未定义的。我在编程指南中没有看到任何关于这一点的内容,有人有参考吗?@jorre“写入共享内存可以缓存”是什么意思?据我所知,没有任何缓存级别的访问延迟小于共享内存。@jorre为了测试为什么选项2工作得更好,我建议监控由于共享内存库冲突而导致的重播次数。我预计选项1会增加。@Young Bae我不确定我是否遵守了。我不认为应该有广播:在选项2中,每个选项都有线程读取(并更新)其唯一的内存位置,该位置在每次迭代中由
tile[rowStartIdx+threadIdx.x+j]
给出(
+threadIdx.x
和使用
blockDim.x
=
warpSz
应确保这一点)@VAndrei据我所知,除非使用
volatile
关键字声明共享内存,.Re-bank冲突:每次迭代一次读-修改-写(一个bank)应该与32次读-修改-写(在这种情况下,我认为仍然是一个bank)一样快,除非这里还有其他原因。
#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();
}