CUDA技術を利用したGPUコンピューティングの実際(後編) ―― FFTを利用した光波の伝播(フレネル回折)をGPUで高速計算
ここではGPU(Graphics Processing Unit)コンピューディングを利用したプログラミングの事例を紹介する.行列の乗算を例に,プログラミングの流れを示す.また,処理を高速化するためのカーネルの実装方法についても解説する.最後にFFT(高速フーリエ変換)を利用するフレネル回折の計算をGPUで行った例を紹介する. (編集部)
1.CUDAを用いたプログラミング
ここでは行列の乗算を例として,CUDAプログラミングの流れを把握していきます.このプロジェクト(CQ_CUDA_matrix)は,Interface誌のWebサイトからダウンロードできます.
行列の乗算はCUDAのプログラミング・ガイド(1)にも例として登場します.行列の乗算は誰もが知っている計算方法で理解しやすいため,本稿でもこの計算を例として取り上げたいと思います.また,行列の乗算には計算量を減らすアルゴリズムが提案されていますが,ここではそのようなアルゴリズムを採用せず,行列の乗算を筆算の要領で行う方法を実装することにしました.
本稿では3種類のカーネルを用意し,プログラミングの仕方によってどの程度,計算速度に差が出るのかを見ていきます.
まず,ホスト側のソース・コードをリスト1に示します.リスト1を見ると,普のC言語とほとんど変わらないことが分かります.
#include <stdio.h>
#include <conio.h>
#include <cutil.h> //CUDAのユーティリティ関連ヘッダ・ファイル
//マクロ宣言
#define BLOCK 16
#define WIDTH 512
//プロトタイプ宣言
void Host(float *a, float *b, float *c);
__global__ void Kernel1(float *a, float *b, float *c);
__global__ void Kernel2(float *a, float *b, float *c);
__global__ void Kernel3(float *a, float *b, float *c);
void SetTimer(unsigned int *t);
float EndTimer(unsigned int *t);
//行列データと計算結果格納用配列
float h_a[WIDTH*WIDTH];
float h_b[WIDTH*WIDTH];
float h_c[WIDTH*WIDTH];
//メイン関数
int main()
{
int i;
unsigned int timer;
//GPUの初期化 ←(1)
CUT_DEVICE_INIT();
//GPU上にメモリを確保 ←(2)
float *d_a, *d_b, *d_c;
cudaMalloc((void**) &d_a, sizeof(float)*WIDTH*WIDTH);
cudaMalloc((void**) &d_b, sizeof(float)*WIDTH*WIDTH);
cudaMalloc((void**) &d_c, sizeof(float)*WIDTH*WIDTH);
cudaMemset(d_c, 0, sizeof(float)*WIDTH*WIDTH);
//d_cを0で初期化する
//行列データ作成 ←(3)
for(i=0; i<WIDTH*WIDTH; i++)
{
h_a[i]=i;
h_b[i]=i;
}
SetTimer(&timer);
//変数をGPU上のメモリへコピー ←(4)
cudaMemcpy(d_a, h_a, sizeof(float)*WIDTH*WIDTH,
cudaMemcpyHostToDevice);
cudaMemcpy(d_b, h_b, sizeof(float)*WIDTH*WIDTH,
cudaMemcpyHostToDevice);
//ブロックとグリッドの定義とカーネルの起動 ←(5)
dim3 grid(WIDTH/BLOCK, WIDTH/BLOCK, 1);
dim3 threads(BLOCK, BLOCK, 1);
Kernel1<<< grid, threads >>>(d_a, d_b, d_c);
↑(カーネル関数名)
//計算結果を取得 ←(6)
cudaMemcpy(h_c, d_c,
sizeof(float)*WIDTH*WIDTH,cudaMemcpyDeviceToHost);
printf("計算時間=%f(ms)",EndTimer(&timer));
printf(" 計算結果=%f\n",h_c[WIDTH*WIDTH-1]);
//GPU上のメモリを開放 ←(7)
cudaFree(d_a);
cudaFree(d_b);
cudaFree(d_c);
//ホスト側での計算(比較用)
SetTimer(&timer);
Host(h_a, h_b, h_c);
printf("ホスト計算時間=%f(ms)",EndTimer(&timer));
printf("計算結果=%f\n",h_c[WIDTH*WIDTH-1]);
getch();
}
リスト1 ホスト側のソース・コード(main.cuファイルからの抜粋)
CUDAを用いたホスト側のプログラミングの流れは,一般に以下のようになります.
- GPUの初期化
- GPU上のデバイス・メモリ領域の確保
- ホストがGPUに計算させるデータを用意
- CUDAのAPI(Application Programming Interface)を使用して,ホストからデバイス・メモリへデータを転送
- スレッド数,ブロック数を指定してカーネルを実行
- ホストがCUDAのAPIを使用してデバイス・メモリ上の計算結果を回収
ここでは,この流れに沿ってリスト1を見ていきます.