GPUプログラミング入門:GPUと並列プログラミング
GPUを使った量子回路シミュレーションを行っていくうえで、GPUの知識はかかせません。ここでは、NVIDIAのGPUとCUDA(Compute Unified Device Architecture)を焦点をあて、GPUの仕組みやプログラミングを見ていきます。まず、GPUとはなにかから。
GPU
GPUの特徴は、並列計算を行うことです。
- 並列計算が必須
物理的なコア数は、1000以上、論理コア数(スレッド)は、数十万以上で、プログラムを並列動作させないと速くはなりません。
コア内部に演算装置があり、されに一つのコアは、同時に複数の計算ができるようになっていて、スレッドとは同時処理できる計算の単位です。
つまり、GPUでは、数十万以上の計算を同時に行えるということになります。 - GPUとCPU間のデータ転送が必須です。
GPUではOSが動作してイませんので、CPUの司令が必要です。CPU・GPUは独立して動きますので、同期させてデータの一貫性を保つ必要があります。
上の図は、GPUを簡略化したものです。GPUは、複数のコアが命令を読み込み、それぞれの命令に必要なデータを読み、命令を実行します。計算結果をメモリに書き込みます。
CPUとは異なり単一命令で複数の演算を同時に行います。
GPUを動作させるには、CPU側から、GPUに向けてデータと命令を転送し、GPUでの計算を実行します。
その際、問題になるのが、データの転送速度です。CPU・GPU間のバスの転送速度は、CPUとメモリ、GPUとメモリ間の転送速度と比較して、相対的に遅いので、一度転送したデータを使い回すことが重要です。
並列計算
並列計算には、タスク並列とデータ並列があります。
- タスク並列は、タスクを分割することによって並列化します。これは、GPUは、不得意です。
- データ並列は、データを分割することによって並列化します。データは異なるが計算の手続きは同じ計算です。GPUが得意で、GPU並列計算の原則はこれです。 プログラムでは、配列とループで記述されます。
データ独立なループ
for(i=0; i < N; i++)
C[i] = A[i] + B[i];
GPUでの高速化は通常、このようなプログラムのループを並列化することで、高速化します。この例は、データが独立しているので、比較的容易に並列化できます。
リダクションが必要なループ
sum = 0;
for(i=0; i < N; i++)
sum += A[i];
このループは、CUDAでリダクションといわれる処理をすることによって高速実装可能ですが、難しくなります。shared memory、warp shuffleといわれる手法を駆使する必要があります。
リダクション
配列Aの足し算結果を、別の配列に保存します。そのとき、他のスレッドの計算の終を待つという同期を行います。この処理を繰り返すことによって、総和をもとめます。これは、一般的にリダクションとよばれます。メモリなどを介してスレッド間でデータのやりとりをすることをスレッド間通信といいます。
スレッドの同期・通信が入ると極端に難しくなります。
データ依存のあるループ
B[0]=0;
for(i=0; i < N; i++)
B[i+1] = B[i] + A[i]:
このループも、CUDAで高速実装可能ですが、リダクションループ同様に、難しくなります。