如果浏览器崩溃,我也会回到我在 2012 年发布的答案。
基本思想是,您可以使用 warp 投票指令执行简单、廉价的缩减,然后使用每个块的零个或一个原子操作来更新主机在每次内核启动后可以读取的固定映射标志。使用映射标志消除了在每次内核启动后显式设备主机传输的需要。
这需要内核中每个 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;
// Do calculations then check for change/convergence
// and set tchanged to be !=0 if required
int idx = blockIdx.x * blockDim.x + threadIdx.x;
unsigned int tchanged = process(inout[idx]);
// Simple blockwise reduction using voting primitives
// increments kchanged is any thread in the block
// returned tchanged != 0
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);
// Loop while the kernel signals it needs to run again
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 中的一个线程将投票结果加载到共享内存中。一个线程减少扭曲结果,然后自动更新kchanged
值。主机线程等待设备完成,然后可以直接从映射的主机变量中读取结果。
您应该能够使其适应您的应用程序需要的任何内容