CUDA合并了对全局内存的访问

时间:2012-04-25 23:30:29

标签: memory cuda copy coalescing

我已阅读过CUDA编程指南,但我错过了一件事。假设我在全局内存中有32位int数组,我想将它复制到具有合并访问权限的共享内存中。 全局数组的索引从0到1024,假设我有4个块,每个块有256个线程。

__shared__ int sData[256];

何时进行合并访问?

1

sData[threadIdx.x] = gData[threadIdx.x * blockIdx.x+gridDim.x*blockIdx.y];

全局内存中的地址被复制为0到255,每个都是32个经线的线程,所以这里可以吗?

2

sData[threadIdx.x] = gData[threadIdx.x * blockIdx.x+gridDim.x*blockIdx.y + someIndex];

如果someIndex不是32的倍数,它不会合并?地址错位?这是对的吗?

4 个答案:

答案 0 :(得分:15)

您最终需要的是您的输入数据是1D还是2D数组,以及您的网格和块是1D还是2D。最简单的情况是1D:

shmem[threadIdx.x] = gmem[blockDim.x * blockIdx.x + threadIdx.x];

这是合并的。我使用的经验法则是将最快速变化的坐标(threadIdx)作为偏移量添加到块偏移量(blockDim * blockIdx)。最终结果是块中线程之间的索引步幅为1.如果步幅变大,则会失去合并。

简单规则(在Fermi和更高版本的GPU上)是如果warp中所有线程的地址落入相同的对齐128字节范围,那么将产生单个内存事务(假设为负载启用了缓存,这是默认值)。如果它们分成两个对齐的128字节范围,则会产生两个内存事务等。

在GT2xx和早期的GPU上,它变得更加复杂。但您可以在编程指南中找到相关的详细信息。

其他例子:

未合并:

shmem[threadIdx.x] = gmem[blockDim.x + blockIdx.x * threadIdx.x];

在GT200及更高版本上没有合并,但也不是太糟糕:

stride = 2;
shmem[threadIdx.x] = gmem[blockDim.x * blockIdx.x + stride * threadIdx.x];

根本没有合并:

stride = 32;
shmem[threadIdx.x] = gmem[blockDim.x * blockIdx.x + stride * threadIdx.x];

合并的2D网格,1D块:

int elementPitch = blockDim.x * gridDim.x;
shmem[threadIdx.x] = gmem[blockIdx.y * elementPitch + 
                          blockIdx.x * blockDim.x + threadIdx.x]; 

合并,2D网格和块:

int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
int elementPitch = blockDim.x * gridDim.x;
shmem[threadIdx.y * blockDim.x + threadIdx.x] = gmem[y * elementPitch + x];

答案 1 :(得分:1)

你的索引为1是错误的(或故意这么奇怪,似乎错了),有些块访问每个线程中的相同元素,因此在这些块中无法合并访问。

<强>证明:

示例:

Grid = dim(2,2,0)

t(blockIdx.x, blockIdx.y)

//complete block reads at 0
t(0,0) -> sData[threadIdx.x] = gData[0];
//complete block reads at 2
t(0,1) -> sData[threadIdx.x] = gData[2];
//definetly coalesced
t(1,0) -> sData[threadIdx.x] = gData[threadIdx.x];
//not coalesced since 2 is no multiple of a half of the warp size = 16
t(1,1) -> sData[threadIdx.x] = gData[threadIdx.x + 2];

如果一个块被合并,那么这是一个“运气”游戏,因此通常

但是合并后的内存读取规则并不像以前那样对新的cuda版本严格 但是对于兼容性问题,如果可能的话,你应该尝试优化内核以获得最低的cuda版本。

以下是一些不错的消息来源:

http://mc.stanford.edu/cgi-bin/images/0/0a/M02_4.pdf

答案 2 :(得分:0)

可以合并访问的规则有些复杂,并且随着时间的推移而发生了变化。每个新的CUDA架构在其可以合并的方面更加灵活。我会说一开始不要担心。相反,以最方便的方式进行内存访问,然后查看CUDA分析器所说的内容。

答案 3 :(得分:-1)

如果您打算使用一维网格和线程几何,那么您的示例是正确的。我认为您打算使用的索引是[blockIdx.x*blockDim.x + threadIdx.x]

使用#1,warp中的32个线程同时执行该指令,因此他们的请求(顺序并与128B(32 x 4)对齐)在特斯拉和费米架构中合并,我相信。

#2,有点模糊。如果someIndex为1,则它不会合并warp中的所有32个请求,但它可能会进行部分合并。我相信Fermi设备将在warp中合并线程1-31的访问,作为128B连续内存段的一部分(并且第一个4B,没有线程需要,浪费)。我认为特斯拉架构设备会因为不对准而使其成为一种非合并的访问,但我不确定。

如果用someIndex作为8,特斯拉将拥有32B对齐的地址,而费米可能将它们分组为32B,64B和32B。但最重要的是,取决于someIndex的价值和架构,发生的事情是模糊的,并不一定会很糟糕。