在CUDA中使用复杂的算法来合并内存访问和全局内存加载/存储效率

时间:2013-01-09 21:13:08

标签: cuda

我正在分析以下CUDA内核

__global__ void fftshift_2D(double2 *data, int N1, int N2)
{
    int i = threadIdx.x + blockDim.x * blockIdx.x;
    int j = threadIdx.y + blockDim.y * blockIdx.y;

    if (i < N1 && j < N2) {
        double a = pow(-1.0, (i+j)&1);

        data[j*blockDim.x*gridDim.x+i].x *= a;
        data[j*blockDim.x*gridDim.x+i].y *= a;
    }
 }

它基本上将2D双精度复数数据矩阵乘以标量双精度变量。

可以看出,我正在执行合并的全局内存访问,我想通过NVIDIA Visual Profiler通过检查全局内存负载和存储效率来验证这一点。令人惊讶的是,这种效率结果恰好是50%,远远低于合并内存访问的预期100%。这是否与复数的实部和虚部的交错存储有关?如果是这样,有什么技巧可以用来恢复100%的效率吗?

提前谢谢。

其他信息

BLOCK_SIZE_x=16
BLOCK_SIZE_y=16

dim3 dimBlock2(BLOCK_SIZE_x,BLOCK_SIZE_y);
dim3 dimGrid2(N2/BLOCK_SIZE_x + (N2%BLOCK_SIZE_x == 0 ? 0:1),N1/BLOCK_SIZE_y + (N1%BLOCK_SIZE_y == 0 ? 0:1));

N1和N2可以是任意偶数。

该卡是NVIDIA GT 540M。

2 个答案:

答案 0 :(得分:5)

看看this NVIDIA Blog Post about efficiency of various memory access patterns。你遇到了Strided Memory Access问题。

由于每个组件都是独立使用的,因此您可以将double2数组视为普通的double数组(就像Robert Crovella suggested一样)。

__global__ void fftshift_2D(double *data, int N1, int N2)
{
    int i = threadIdx.x + blockDim.x * blockIdx.x;
    int j = threadIdx.y + blockDim.y * blockIdx.y;

    if (i < N1 * 2 && j < N2) {
        double a = pow(-1.0, (i / 2 + j)&1);
        data[j*blockDim.x*gridDim.x+i] *= a;
    }
}

但如果您需要同时访问x&amp;您可以尝试在单个线程中使用y组件:

使用2个独立的数组。一个是x组件,一个是y组件。像那样:

__global__ void fftshift_2D(double *dataX, double *dataY, int N1, int N2)
{
    int i = threadIdx.x + blockDim.x * blockIdx.x;
    int j = threadIdx.y + blockDim.y * blockIdx.y;

    if (i < N1 && j < N2) {
        double a = pow(-1.0, (i+j)&1);

        dataX[j*blockDim.x*gridDim.x+i] *= a;
        dataY[j*blockDim.x*gridDim.x+i] *= a;
    }
}

或者按原样保留数据布局,但是将其加载到共享内存并将其从共享内存重新洗牌。这或多或少看起来像那样:

__global__ void fftshift_2D(double2 *data, int N1, int N2)
{
    __shared__ double buff[BLOCK_SIZE*2];
    double2 *buff2 = (double2 *) buff;
    int i = threadIdx.x + blockDim.x * blockIdx.x;
    int j = threadIdx.y + blockDim.y * blockIdx.y;
    double ptr = (double *) &data[j*blockDim.x*gridDim.x + blockDim.x * blockIdx.x];

    // TODO add guarding with N1 & N2
    buff[threadIdx.x] = ptr[threadIdx.x];
    buff[blockDim.x + threadIdx.x] = ptr[blockDim.x + threadIdx.x];
    __syncthreads();

    double a = pow(-1.0, (i+j)&1);
    buff2[threadIdx.x].x *= a 
    buff2[threadIdx.x].y *= a 

    __syncthreads();
    ptr[threadIdx.x] = buff[threadIdx.x];
    ptr[blockDim.x + threadIdx.x] = buff[blockDim.x + threadIdx.x];
}

答案 1 :(得分:4)

是的,因为您有一组结构数据存储格式,并且您只使用以下行引用所有其他元素:

    data[j*blockDim.x*gridDim.x+i].x *= a;

然后,全局负载和因此发生的全局存储将各自仅具有50%的利用率。请注意,我认为缓存应该对此有所帮助,因为您引用了以下行中的备用元素。但负载/存储效率仍为50%。

我相信你可以解决这个问题(对于这个特定的例子)使用某种方法来重铸*data

double *mydata = (double *)data;
...
mydata[2*(j*blockDim.x*gridDim.x)+i] *= a;

请注意我并没有试图准确地说明如何获得相同的报道,只是说明了这个想法。上面的代码大概是需要的,但是你需要调整代码以确保你想要成倍增加的所有元素都得到正确处理。