高效使用cuda共享内存存储字符

高效使用cuda共享内存存储字符,c,cuda,gpu,gpgpu,C,Cuda,Gpu,Gpgpu,假设我必须处理8位图像像素。我想分配共享内存来存储这些像素值,并在内核中使用 现在的问题是共享内存库中的内存是以32位分配的。一个字符(8位像素值)将由24个零的序列填充存储。这将导致巨大的内存损失 那么,在共享内存中存储像素值,避免内存浪费的最佳方法是什么呢 使用结构在32位块上存储4个像素。 处理每个线程的整个块,以避免组冲突和非合并访问 typedef struct { unsigned char pixels[4]; } FourPixels; __global__ void my

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

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


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

使用结构在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);
}
typedef结构
{
无符号字符像素[4];
}四像素;
__全局无效myKernel(四像素*gpixels)
{
外部共享四像素像素像素[];
int id=blockIdx.x*blockDim.x+threadIdx.x;
//在共享内存上复制
像素[id]=gpixels[id];
//示例:删除蓝色组件
像素[0]&=0xFC;
像素[1]&=0xFC;
像素[2]&=0xFC;
像素[3]&=0xFC;
//在全局内存上复制结果
gpixels[id]=spixels[id];
}
__主机_uu_uu_uu_u_u_u_u_u_u_u_u_u_u_u_u_u_u_u
{
四像素*mypix;
cudaMalloc(&mypixs,4*sizeof(四像素));
myKernel(mypixs);//16像素!
cudaDeviceSynchronize();
cudaFree(mypixs);
}

可能有一些误解,所以我想我也应该添加一个答案,而不是更多的评论。作为前言,让我声明我不打算详细解释银行冲突是如何产生的。如果你想了解这一点,你可以参加网络研讨会,还有很多其他问题

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

    __shared__ char my_chars[4096];
    
    spixels[id] = gpixels[id];
    
    上述声明中的所有字节都将连续打包,没有中间填充。无论我们如何访问这样的数组,这都是正确的

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

    __shared__ char my_chars[4096];
    
    spixels[id] = gpixels[id];
    
    假设编译器将结构副本标识为4字节传输,这将有更好的内存带宽利用率(在全局端和共享端)

  • 从银行冲突的角度来看,银行冲突产生于访问模式,而不是数据存储在内存中的方式。在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像素结构”方法不能防止这种糟糕的访问模式


  • 每个银行区块存储4个像素?@Michael你能给出一个示例代码来演示你的想法吗?你认为这不会导致银行冲突吗?是的,这会导致银行冲突,除非你对每个线程处理4个像素。@Michael,那么你的意思是我将使用具有4个像素成员的结构?你能给出一行代码来证明你的想法并把它作为一个答案吗?“一个字符…将被存储为24个零的序列”不确定你在这里得到了什么。这是不对的。如果您定义
    \uuuuuuuuuuuuuuuu共享\uuuuuuuuuu字符我的字符[32]共享内存中将有一个32个字符的序列,占据8个连续的32位位置,没有填充。非常好。此外,还可以从内核调用
    \uuu设备\uuu
    函数,以避免每个字节的代码重复。@RogerDahl您在上面的代码中说的是“删除蓝色组件”吗?@gpuguy:是的。对于像特定示例这样非常简单的事情,单独的函数可能没有意义,但如果重复的代码超过一两行,我会创建一个设备函数。@Robert关于你的第2点,你谈到了cacheline。寄存器和共享内存之间是否有缓存?我怀疑在共享内存上下文中是否存在合并和非合并访问的问题。你是对的。我已删除对缓存的引用。然而,全局内存访问合并的概念与库与共享内存冲突的概念有关。导致全局内存合并的访问模式也倾向于避免共享内存中的银行冲突。但是,概念也不同。关于上面的2(更新的)wrt Michaels代码,我知道在任何时间点线程0和线程1(仅考虑两个线程应用程序)都不会访问连续的内存位置,由于每个结构的第一个成员将不相邻,那么,根据Michael的代码,带宽利用率更高的原因是什么(还要注意,每个线程都在串行访问4个值)?为了大家的眼睛,如果你能在答案中给出解释,我将不胜感激。我并没有说迈克尔密码的每一个方面都更好,即使是考虑到这一点