我将回到2012年本应发布的答案,但因为浏览器崩溃而未能发表。
基本思想是使用warp投票指令执行简单、廉价的reduction操作,然后每个块只需使用零或一次原子操作来更新一个固定的映射标志,主机可以在每个内核启动后读取该标志。使用映射标志可消除每次内核启动后需要显式设备到主机传输的需要。
此操作需要每个内核中每个warp使用一个共享内存字,这是一个小的开销,如果您提供了每个块中warp数量的模板参数,一些模板技巧可以实现循环展开。
完整的工作示例(带有C++主机代码,我目前无法访问工作的PyCUDA安装)如下:
#include <cstdlib>
#include <vector>
#include <algorithm>
#include <assert.h>
__device__ unsigned int process(int & val)
{
return (++val < 10);
}
template<int nwarps>
__global__ void kernel(int *inout, unsigned int *kchanged)
{
__shared__ int wchanged[nwarps];
unsigned int laneid = threadIdx.x % warpSize;
unsigned int warpid = threadIdx.x / warpSize;
int idx = blockIdx.x * blockDim.x + threadIdx.x;
unsigned int tchanged = process(inout[idx]);
tchanged = __any(tchanged != 0);
if (laneid == 0) {
wchanged[warpid] = tchanged;
}
__syncthreads();
if (threadIdx.x == 0) {
int bchanged = 0;
#pragma unroll
for(int i=0; i<nwarps; i++) {
bchanged |= wchanged[i];
}
if (bchanged) {
atomicAdd(kchanged, 1);
}
}
}
int main(void)
{
const int N = 2048;
const int min = 5, max = 15;
std::vector<int> data(N);
for(int i=0; i<N; i++) {
data[i] = min + (std::rand() % (int)(max - min + 1));
}
int* _data;
size_t datasz = sizeof(int) * (size_t)N;
cudaMalloc<int>(&_data, datasz);
cudaMemcpy(_data, &data[0], datasz, cudaMemcpyHostToDevice);
unsigned int *kchanged, *_kchanged;
cudaHostAlloc((void **)&kchanged, sizeof(unsigned int), cudaHostAllocMapped);
cudaHostGetDevicePointer((void **)&_kchanged, kchanged, 0);
const int nwarps = 4;
dim3 blcksz(32*nwarps), grdsz(16);
do {
*kchanged = 0;
kernel<nwarps><<<grdsz, blcksz>>>(_data, _kchanged);
cudaDeviceSynchronize();
} while (*kchanged != 0);
cudaMemcpy(&data[0], _data, datasz, cudaMemcpyDeviceToHost);
cudaDeviceReset();
int minval = *std::min_element(data.begin(), data.end());
assert(minval == 10);
return 0;
}
在这里,kchanged
是内核用来向主机发出需要再次运行的信号标志。内核运行直到输入中的每个条目都增加到超过阈值为止。在每个线程处理结束时,它参与了一个warp投票,之后每个warp中的一个线程将投票结果加载到共享内存中。一个线程减少了warp结果,然后原子更新了kchanged
值。主机线程等待设备完成,然后可以直接从映射的主机变量中读取结果。
您应该能够根据您的应用程序要求进行适应。
__any()
warp vote)来高效地完成它。然后,您只需要对每个块内的每个warp的结果进行非常简单的约简,并对每个块进行单个原子添加以更新全局标志。如果标志位于零拷贝内存中,则无需显式复制即可检查主机上的结果。 - talonmies