また、2012年に投稿したであろう回答に戻りますが、ブラウザーがクラッシュします。
基本的な考え方は、ワープ投票命令を使用して単純で安価な削減を実行し、ブロックごとにゼロまたは1つのアトミック操作を使用して、カーネルの起動ごとにホストが読み取ることができる固定されたマップ済みフラグを更新できることです。マップされたフラグを使用すると、カーネルの起動ごとに転送をホストする明示的なデバイスが不要になります。
これには、カーネルのワープごとに1ワードの共有メモリが必要です。これは小さなオーバーヘッドであり、テンプレートパラメータとしてブロックごとのワープの数を指定すると、いくつかのテンプレートトリックでループ展開が可能になります。
完全に機能する試験版(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
は、カーネルがホストに対して再度実行する必要があることを通知するために使用するフラグです。カーネルは、入力の各エントリがしきい値を超えるまでインクリメントされるまで実行されます。各スレッドの処理の最後に、ワープ投票に参加します。その後、各ワープの1つのスレッドが、投票結果を共有メモリにロードします。1つのスレッドがワープの結果を減らしてから、kchanged
値をアトミックに更新します。ホストスレッドは、デバイスが終了するまで待機し、マップされたホスト変数から結果を直接読み取ることができます。
アプリケーションが必要とするものにこれを適応させることができるはずです