您最终想要什么取决于您的输入数据是一维数组还是二维数组,以及您的网格和块是一维还是二维。最简单的情况都是一维的:
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];