高效使用cuda共享内存存储字符
假设我必须处理8位图像像素。我想分配共享内存来存储这些像素值,并在内核中使用 现在的问题是共享内存库中的内存是以32位分配的。一个字符(8位像素值)将由24个零的序列填充存储。这将导致巨大的内存损失高效使用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
那么,在共享内存中存储像素值,避免内存浪费的最佳方法是什么呢 使用结构在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];
上述声明中的所有字节都将连续打包,没有中间填充。无论我们如何访问这样的数组,这都是正确的char
数组定义,我们也不需要特殊的struct
来实现这一点,但Michael建议的4像素结构定义显然说明了如何实现这一点。请注意,在本参考资料中,我并不是说Michael代码的每个方面都会带来更好的性能。显然,当访问单个像素时,我们不是在讨论32位对8位,所以我在这里的评论不适用于Michael的代码。但是,例如,在Michael的代码中,有一行将数据从全局内存传输到共享内存:
__shared__ char my_chars[4096];
spixels[id] = gpixels[id];
假设编译器将结构副本标识为4字节传输,这将有更好的内存带宽利用率(在全局端和共享端)__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个值)?为了大家的眼睛,如果你能在答案中给出解释,我将不胜感激。我并没有说迈克尔密码的每一个方面都更好,即使是考虑到这一点