CUDAは、単一命令複数データ(SIMD)プログラミングモデルを活用します。計算スレッドはブロックに編成され、スレッドブロックは別のストリーミングマルチプロセッサ(SM)に割り当てられます。SMでのスレッドブロックの実行は、スレッドをワープのスレッドに配置することによって実行され32
ます。各ワープはロックステップで動作し、異なるデータに対してまったく同じ命令を実行します。
一般に、GPUをいっぱいにするために、カーネルは、実際にSMでホストできるはるかに多くのブロックで起動されます。すべてのブロックをSMでホストできるわけではないため、作業スケジューラは、ブロックの計算が終了したときにコンテキストスイッチを実行します。ブロックの切り替えはスケジューラーによって完全にハードウェアで管理され、プログラマーにはブロックがSMにスケジュールされる方法に影響を与える手段がないことに注意してください。これにより、SIMDプログラミングモデルに完全に適合せず、作業の不均衡が生じるすべてのアルゴリズムの制限が明らかになります。実際、ブロックの最後のスレッドの実行が終了するまで、ブロックは同じSM上のA
別のブロックに置き換えられません。B
A
CUDAはハードウェアスケジューラーをプログラマーに公開しませんが、永続スレッドスタイルはワークキューに依存することでハードウェアスケジューラーをバイパスします。ブロックが終了すると、キューでさらに作業がないかチェックし、作業がなくなるまでチェックを続けます。作業がなくなると、ブロックは終了します。このようにして、カーネルは、使用可能なSMの数と同じ数のブロックで起動されます。
永続スレッドの手法は、プレゼンテーションから抜粋した次の例でよりよく示されています。
「GPGPU」コンピューティングとCUDA/OpenCLプログラミングモデル
別のより詳細な例は、論文で利用可能です
GPUでのレイトラバーサルの効率を理解する
// Persistent thread: Run until work is done, processing multiple work per thread
// rather than just one. Terminates when no more work is available
// count represents the number of data to be processed
__global__ void persistent(int* ahead, int* bhead, int count, float* a, float* b)
{
int local_input_data_index, local_output_data_index;
while ((local_input_data_index = read_and_increment(ahead)) < count)
{
load_locally(a[local_input_data_index]);
do_work_with_locally_loaded_data();
int out_index = read_and_increment(bhead);
write_result(b[out_index]);
}
}
// Launch exactly enough threads to fill up machine (to achieve sufficient parallelism
// and latency hiding)
persistent<<numBlocks,blockSize>>(ahead_addr, bhead_addr, total_count, A, B);