假设我必须处理8位图像像素。我想分配共享内存来存储这些像素值,并在我的内核中使用。
现在的问题是共享内存库中的内存是以32位分配的。字符(8位像素值)将以24个零的序列进行填充。这将导致巨大的内存丢失。
那么将像素值存储在共享内存中的最佳方法是什么,避免浪费内存?
答案 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的其他问题。
从存储空间效率的角度来看,存储char
(或unsigned char
数组的效率并不缺乏,我将在此讨论中使用char
但在共享内存中,这两个讨论之间没有区别:
__shared__ char my_chars[4096];
上述声明中的所有字节将被连续打包,没有插入填充。无论我们如何访问这样的阵列,都是如此。
从内存带宽利用率的角度来看,每个线程访问32位或每个线程64位将始终提供最大的内存带宽利用率,因此每个线程4像素/字节/字符的分组将有助于此。请注意,即使使用我的struct
数组定义,我们也不需要特殊的char
来完成此操作,但迈克尔建议的4像素结构定义肯定会明确如何执行此操作。请注意,在此参考中,我并不是说迈克尔代码的每个方面都会带来更好的性能。显然,当访问单个像素时,我们不是在谈论32位与8位,所以我的评论不适用于迈克尔的代码。但是,例如,在Michael的代码中,有一条线将数据从全局内存传输到共享内存:
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像素结构”方法不能防止这种糟糕的访问模式。