GPU共享内存实用示例

时间:2017-04-03 23:25:42

标签: cuda gpu-shared-memory bank-conflict

我有一个这样的数组:

data[16] = {10,1,8,-1,0,-2,3,5,-2,-3,2,7,0,11,0,2}

我想在G80 GPU上使用共享内存来计算此阵列的减少量。

NVIDIA文档中引用的内核是这样的:

__global__ void reduce1(int *g_idata, int *g_odata) {
extern __shared__ int sdata[];

unsigned int tid = threadIdx.x;
unsigned int i = blockIdx.x*blockDim.x + threadIdx.x;
sdata[tid] = g_idata[i];
__syncthreads();

// here the reduction :

for (unsigned int s=1; s < blockDim.x; s *= 2) {
int index = 2 * s * tid;
if (index < blockDim.x) {
sdata[index] += sdata[index + s];
}
__syncthreads();
}

该报的作者说,这种方法存在银行冲突问题。我试着理解,但我想不明白为什么?我知道银行冲突和广播访问的定义,但仍然无法理解这一点。

Bank Conflicts

1 个答案:

答案 0 :(得分:2)

G80处理器是第一代CUDA GPU中非常古老的支持CUDA的GPU,其计算能力为1.0。最近的CUDA版本(6.5之后)不再支持这些设备,因此在线文档不再包含了解这些设备中银行结构的必要信息。

因此,我将从CUDA 6.5 C编程指南中摘录cc 1.x设备的必要信息:

  

G.3.3。共享内存

     

共享内存有16个组织,以便连续的32位字映射   连续的银行。每个存储区每两个时钟周期带宽为32位。

     

warp的共享内存请求被分成两个内存请求,每个请求一个   半翘曲,是独立发布的。结果,就没有银行   属于warp的前半部分的线程和属于的线程之间的冲突   同一个经线的下半部分。

在这些设备中,共享存储器具有16个存储体结构,使得每个存储体具有宽度&#34; 32位或4字节。例如,每个银行的宽度与intfloat数量相同。因此,我们设想可能存储在这种共享内存中的前32个4字节数量及其相应的库(使用f代替sdata作为数组名称):

extern __shared__ int f[];

index: f[0] f[1] f[2] f[3] ... f[15] f[16] f[17] f[18] f[19] ... f[31]
bank:    0    1    2    3  ...   15     0     1     2     3  ...   15

共享内存中的前16个int数量属于0到15的存储区,共享内存中的下一个16 int数量也属于0到15的存储区(依此类推,如果有的话)我们int数组中的更多数据。

现在让我们看一下会引发银行冲突的代码行:

for (unsigned int s=1; s < blockDim.x; s *= 2) {
int index = 2 * s * tid;
if (index < blockDim.x) {
sdata[index] += sdata[index + s];
}

让我们考虑第一次通过上述循环,其中s为1.这意味着index2*1*tid,因此对于每个帖子,index只是threadIdx.x的价值的两倍:

threadIdx.x: 0 1 2 3 4  5  6  7  8  9 10 11 ...
 index:      0 2 4 6 8 10 12 14 16 18 20 22 ...
 bank:       0 2 4 6 8 10 12 14  0  2  4  6 ...

所以对于这个读操作:

+= sdata[index + s]

我们有:

threadIdx.x: 0 1 2 3 4  5  6  7  8  9 10 11 ...
 index:      0 2 4 6 8 10 12 14 16 18 20 22 ...
 index + s:  1 3 5 7 9 11 13 15 17 19 21 23 ...
 bank:       1 3 5 7 9 11 13 15  1  3  5  7 ...

因此,在前16个线程中,我们有两个想要从bank 1读取的线程,两个想要从bank 3读取的线程,两个想要从bank 5读取的线程,等等。这个读取周期因此遇到2-方式银行在第一个16线程组中发生冲突。请注意,同一行代码上的其他读写操作同样存在冲突:

sdata[index] +=

因为这会读取,然后写入0,2,4等银行两次每组16个线程。

请注意可能正在阅读此示例的其他人:如上所述,它与cc 1.x设备有关。证明cc 2.x和更新设备上的银行冲突的方法可能类似,但由于扭曲执行差异以及这些新设备具有32路银行结构而不是16路银行的事实,具体情况不同结构