由于所有线程都尝试同时更新相同的全局内存地址,您现在正在执行的实现会受到竞争条件的影响。您可以轻松地放置一个atomicAdd
函数,*error += pow...
但由于每次更新都会对其进行序列化,因此会出现性能问题。
相反,您应该尝试使用共享内存进行减少,如下所示:
_global__ void kSquaredError(double* data, double* recon, double* error, unsigned int num_elements) {
const unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
const unsigned int tid = threadIdx.x;
__shared__ double serror[blockDim.x];//temporary storage of each threads error
for (unsigned int i = idx; i < num_elements; i += blockDim.x * gridDim.x) {
serror[tid] += pow(data[i] - recon[i], 2);//put each threads value in shared memory
}
__syncthreads();
int i = blockDim.x >> 1; //halve the threads
for(;i>0;i>>=1) {//reduction in shared memory
if(tid<i) {
serror[tid] += serror[tid+i];
__syncthreads();//make shure all threads are at the same state and have written to shared memory
}
}
if(tid == 0) {//thread 0 updates the value in global memory
atomicAdd(error,serror[tid]);// same as *error += serror[tid]; but atomic
}
}
它的工作原理如下,每个线程都有自己的临时变量,它计算所有输入的错误总和,当它完成时,所有线程都聚集在__syncthreads
指令处,以确保所有数据都是完整的。
现在,块中所有线程的一半将从对应的另一半中获取一个值,将其添加到自己的值中,再次一半的线程并再次执行,直到剩下一个线程(线程 0),该线程将具有总和.
现在线程 0 将使用 atomicAdd 函数更新全局内存,以避免与其他块(如果有的话)发生争用情况。
如果我们只使用第一个示例并在每个分配上使用 atomicAdd 。您将拥有gridDim.x*blockDim.x*num_elements
可以序列化的原子函数,现在我们只有gridDim.x
原子函数,数量要少得多。
请参阅优化 CUDA 中的并行缩减以进一步了解如何使用 cuda 进行缩减。
编辑
在减少 for 循环中添加了 if 语句,忘记了。