有效地使用cuda共享内存来存储字符

时间:2013-11-27 09:09:25

标签: c cuda gpu gpgpu

假设我必须处理8位图像像素。我想分配共享内存来存储这些像素值,并在我的内核中使用。

现在的问题是共享内存库中的内存是以32位分配的。字符(8位像素值)将以24个零的序列进行填充。这将导致巨大的内存丢失。

那么将像素值存储在共享内存中的最佳方法是什么,避免浪费内存?

2 个答案:

答案 0 :(得分:4)

使用结构在32位块上存储4个像素 每个线程处理整个块以避免银行冲突和非合并访问。

typedef struct
{
  unsigned char pixels[4];
} FourPixels;

__global__ void myKernel(FourPixels* gpixels)
{
  extern __shared__ FourPixels spixels[];

  int id = blockIdx.x * blockDim.x + threadIdx.x;

  //copy on shared memory
  spixels[id] = gpixels[id];

  //example : remove blue component
  spixels[id].pixels[0] &= 0xFC;
  spixels[id].pixels[1] &= 0xFC;
  spixels[id].pixels[2] &= 0xFC;
  spixels[id].pixels[3] &= 0xFC;

  //copy result on global memory
  gpixels[id] = spixels[id];
}

__host__ int main()
{
  FourPixels* mypixs;
  cudaMalloc(&mypixs, 4*sizeof(FourPixels));

  myKernel<<<1, 4, 4*sizeof(FourPixels)>>>(mypixs); // 16 pixels !
  cudaDeviceSynchronize();

  cudaFree(mypixs);
}

答案 1 :(得分:2)

可能存在一些误解,所以我想我也会添加一个答案,而不是更多的评论。作为序言,请允许我指出,我不打算详细解释银行冲突是如何产生的。如果你想了解的话,你可以参加网络研讨会,还有很多关于SO的其他问题。

  1. 从存储空间效率的角度来看,存储char(或unsigned char数组的效率并不缺乏,我将在此讨论中使用char但在共享内存中,这两个讨论之间没有区别:

    __shared__ char my_chars[4096];
    

    上述声明中的所有字节将被连续打包,没有插入填充。无论我们如何访问这样的阵列,都是如此。

  2. 从内存带宽利用率的角度来看,每个线程访问32位或每个线程64位将始终提供最大的内存带宽利用率,因此每个线程4像素/字节/字符的分组将有助于此。请注意,即使使用我的struct数组定义,我们也不需要特殊的char来完成此操作,但迈克尔建议的4像素结构定义肯定会明确如何执行此操作。请注意,在此参考中,我并不是说迈克尔代码的每个方面都会带来更好的性能。显然,当访问单个像素时,我们不是在谈论32位与8位,所以我的评论不适用于迈克尔的代码。但是,例如,在Michael的代码中,有一条线将数据从全局内存传输到共享内存:

    spixels[id] = gpixels[id];
    

    假设编译器将结构副本识别为4字节传输,这将具有更好的内存带宽利用率(在全局和共享端)。

  3. 从银行冲突的角度来看,银行冲突是由访问模式引起的,主要不是数据存储在内存中的方式。在cc 2.0和更新的设备上,以前的存储和访问模式定义:

    __shared__ char my_chars[4096];
    int idx=threadIdx.x+blockDim.x*blockIdx.x;
    char my_pixel = my_chars[idx]; 
    

    会引发银行冲突。基于访问模式可能会出现银行冲突,例如:

    char my_pixel = my_chars[128*idx];
    

    将导致32路银行冲突的病态案例。但是如果我们每个线程访问32位,则可以构造类似的病理访问模式; “4像素结构”方法不能防止这种糟糕的访问模式。