使用CUDA共享内存改善全局访问模式

时间:2012-11-05 23:45:14

标签: cuda shared-memory

我有以下内核来获取一堆向量的大小:

__global__ void norm_v1(double *in, double *out, int n)
{
    const uint i = blockIdx.x * blockDim.x + threadIdx.x;

    if (i < n)
    {
        double x = in[3*i], y = in[3*i+1], z = in[3*i+2];
        out[i] = sqrt(x*x + y*y + z*z);
    }
}

然而,由于将in打包为[x0,y0,z0,...,xn,yn,zn],因此使用分析器表示32%的全局负载效率表现不佳。将数据重新打包为[x0, x1, ..., xn, y0, y1, ..., yn, z0, z1, ..., zn]会大大改善事情(xyz的偏移会相应地改变。运行时间缩短,效率高达100%。

然而,这种包装对我的应用来说根本不实用。因此,我希望调查共享内存的使用。我的想法是让一个块中的每个线程从全局内存中复制三个值(blockDim.x) - 产生合并访问。在最大blockDim.x = 256的假设下,我提出了:

#define BLOCKDIM 256

__global__ void norm_v2(double *in, double *out, int n)
{
    __shared__ double invec[3*BLOCKDIM];

    const uint i = blockIdx.x * blockDim.x + threadIdx.x;

    invec[0*BLOCKDIM + threadIdx.x] = in[0*BLOCKDIM+i];
    invec[1*BLOCKDIM + threadIdx.x] = in[1*BLOCKDIM+i];
    invec[2*BLOCKDIM + threadIdx.x] = in[2*BLOCKDIM+i];
    __syncthreads();

    if (i < n)
    {
        double x = invec[3*threadIdx.x];
        double y = invec[3*threadIdx.x+1];
        double z = invec[3*threadIdx.x+2];

        out[i] = sqrt(x*x + y*y + z*z);
    }
}

然而,n % blockDim.x != 0显然存在缺陷,需要提前知道最大blockDim,并在使用out[i > 255]进行测试时为n = 1024生成错误的结果。我该如何最好地解决这个问题呢?

1 个答案:

答案 0 :(得分:1)

我认为这可以解决out[i > 255]问题:

__shared__ double shIn[3*BLOCKDIM];

const uint blockStart = blockIdx.x * blockDim.x;

invec[0*blockDim.x+threadIdx.x] = in[ blockStart*3 + 0*blockDim.x + threadIdx.x];
invec[1*blockDim.x+threadIdx.x] = in[ blockStart*3 + 1*blockDim.x + threadIdx.x];
invec[2*blockDim.x+threadIdx.x] = in[ blockStart*3 + 2*blockDim.x + threadIdx.x];
__syncthreads();

double x = shIn[3*threadIdx.x];
double y = shIn[3*threadIdx.x+1];
double z = shIn[3*threadIdx.x+2];

out[blockStart+threadIdx.x] = sqrt(x*x + y*y + z*z);

至于n % blockDim.x != 0我建议用0填充输入/输出数组以匹配要求。

如果您不喜欢BLOCKDIM宏 - 请使用extern __shared__ shArr[]进行探索,然后将第3个参数传递给内核配置:

norm_v2<<<gridSize,blockSize,dynShMem>>>(...)

dynShMem是动态共享内存使用量(以字节为单位)。这是额外的共享内存池,其大小在运行时指定,其中所有extern __shared__变量将最初分配给。


你使用的是什么GPU? Fermi或Kepler 可以通过L1缓存来帮助您的原始代码。


如果您不想填充in数组,或者您最终在其他地方执行类似技巧,则可能需要考虑实施设备端memcopy,如下所示:< / p>

template <typename T>
void memCopy(T* destination, T* source, size_t numElements) {
    //assuming sizeof(T) is a multiple of sizeof(int)
    //assuming one-dimentional kernel (only threadIdx.x and blockDim.x matters) 
    size_t totalSize = numElements*sizeof(T)/sizeof(int);
    int* intDest = (int*)destination;
    int* intSrc = (int*)source;
    for (size_t i = threadIdx.x; i < totalSize; i += blockDim.x) {
        intDest[i] = intSrc[i];
    }
    __syncthreads();
}

它基本上将任何数组视为int - s的数组,并将数据从一个位置复制到另一个位置。如果您只使用64位类型,则可能希望将基础int类型替换为double - s或long long int

然后您可以用以下内容替换复制行:

memCopy(invec, in+blockStart*3, min(blockDim.x, n-blockStart));