我正在制作我的游戏项目(塔防),我试图使用共享内存计算所有critters
和JCuda塔之间的距离。对于每个塔,我运行一个带有N个线程的块,其中N等于地图上critters
的数量。我正在计算给定块中所有critters
与该塔之间的距离,并将迄今为止找到的最小距离存储在块的共享内存中。我当前的代码是这样的:
extern "C"
__global__ void calcDistance(int** globalInputData, int size, int
critters, int** globalQueryData, int* globalOutputData) {
//shared memory
__shared__ float minimum[2];
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = blockIdx.y;
if (x < critters) {
int distance = 0;
//Calculate the distance between tower and criter
for (int i = 0; i < size; i++) {
int d = globalInputData[x][i] - globalQueryData[y][i];
distance += d * d;
}
if (x == 0) {
minimum[0] = distance;
minimum[1] = x;
}
__syncthreads();
if (distance < minimum[0]) {
minimum[0] = distance;
minimum[1] = x;
}
__syncthreads();
globalOutputData[y * 2] = minimum[0];
globalOutputData[y] = minimum[1];
}
}
问题是,如果我使用相同的输入多次重新运行代码(每次运行后我释放主机和设备上的所有内存),我每次执行代码时都会得到不同的输出块(塔)号>27日……我很确定它与共享内存和我处理它的方式有关,因为重写代码以使用全局内存在代码执行时给出相同的结果。什么好主意吗?
内核中存在内存争用问题(即写后读正确性):
if (distance < minimum[0]) {
minimum[0] = distance;
minimum[1] = x;
}
执行时,块中的每个线程都会尝试同时读取和写入最小值。不能保证当一个warp中的多个线程尝试写入相同的共享内存位置时会发生什么,也不能保证同一块中的其他warp在从正在写入的内存位置加载时将读取什么值。内存访问不是原子的,并且没有锁定或序列化,这将确保代码执行你似乎想要做的缩减操作类型。
同样问题的一个比较温和的版本适用于内核末尾回写全局内存:
__syncthreads();
globalOutputData[y * 2] = minimum[0];
globalOutputData[y] = minimum[1];
写操作之前的屏障确保在"最终"(尽管不一致)值被存储在最小值之前完成对最小值的写操作,但是块中的每个线程都将执行写操作。
如果您的意图是让每个线程计算一个距离,然后将块上的最小距离值写入全局内存,则必须使用原子内存操作(对于共享内存,这在compute 1.2/1.3和2上是支持的)。(仅限X设备),或者写一个显式的共享内存缩减。在此之后,只有一个线程应该执行写回全局内存。
最后,还有一个可能导致内核挂起的潜在同步正确性问题。__syncthreads()
(它映射到PTX条指令)要求块中的每个线程在内核继续之前到达并执行该指令。有这样的控制流:
if (x < critters) {
....
__syncthreads();
....
}
如果块中的一些线程可以绕过屏障进行分支并退出,而其他线程在屏障处等待,则将导致内核挂起。在__syncthreads()调用周围不应该有任何分支分歧,以确保CUDA内核的执行正确性。
因此,总结一下,回到绘图板上,至少解决了当前代码中的三个问题。