GPUで並列処理をやってみる
この間のインタプリタをはじめから・・・で問題がありご指摘を受けました。
ですので次回より修正を行い、今回は GPGPU を使った計算についてやりたいと思います。
今回 nVidia の GeForce 8800 GTX というボードが手に入りましたので、専用の言語 (現在 GF8x 系のみで動作可能) であるCUDAを利用したいと思います。
1.処理の流れ
という流れで処理を行います。
注意しなければならない点としてGPUからCPUにメモリ内容をコピーする際、GPU内部で出力用メモリに書き出しが行われなかった場合、前回の出力結果とまったく同じものが出てきます。 (再起動してもフラッシュされない場合もありました。)
2.実行中の流れ
本家サンプルなどにあるGPU処理の呼び出しでは
関数名 <<< BLOCK_NUM, THREADS_NUM >>> 関数に渡す引
となっています。
BLOCK_NUM
は1つずつマルチプロセッサ (今回は16個) に置かれて並列実行されます。
また、それぞれのブロックの中で THREADS_NUM
の回数分スレッドが実行されます。
3.コーディング
とりあえず軽めに 44 行列の掛け算を300回ほど計算してもらいました。
//main.cu #include <stdio.h> #include <time.h> #include <cutil.h> #include "kernel.cu" #define THREADS_NUM 300 #define BLOCKS_NUM 4 void Run(int argc, char** argv) { // 初期化 CUT_DEVICE_INIT(); int nSize = 600; int n; n = sizeof(Test)nSize; printf("size = %d\n", n); // CPU側のデータを準備 // 乗算されるデータ Test h_sd1; h_sd1 = (Test)malloc(n / 2); // 乗算するデータ Test h_sd2; h_sd2 = (Test)malloc(n / 2); // 返り値の代入用 Test h_sd3; h_sd3 = (Test)malloc(n / 2); int i; // 値の準備 for(i=0; i<nSize/2; i++){ for(int j = 0; j < 4; j++) { Obj.col[j] = make_float4(rand()%10, rand()%10, rand()%10, rand()%10); h_sd1[i].col[j] = Obj.col[j]; Obj.row[j] = make_float4(rand()%10, rand()%10, rand()%10, rand()%10); h_sd2[i].row[j] = Obj.row[j]; Obj.col[j] = make_float4(0, 0, 0, 0); h_sd3[i] = Obj; } } printf("\n"); // GPU側メモリの準備 // テストデータ Test d_sd1; // cudaMalloc(pointer, size) CUDA_SAFE_CALL(cudaMalloc((void**)&d_sd1, n / 2)); // cudaMemcpy(dist, from, size, CPU->GPU) CUDA_SAFE_CALL(cudaMemcpy(d_sd1, h_sd1, n / 2, cudaMemcpyHostToDevice)); Test d_sd2; // cudaMalloc(pointer, size) CUDA_SAFE_CALL(cudaMalloc((void)&d_sd2, n / 2)); // cudaMemcpy(dist, from, size, CPU->GPU) CUDA_SAFE_CALL(cudaMemcpy(d_sd2, h_sd2, n / 2, cudaMemcpyHostToDevice)); // 値を得るためのデータ代入用 Test* d_sd3; CUDA_SAFE_CALL(cudaMalloc((void)&d_sd3, n/2)); // スレッド数 dim3 threads(THREADS_NUM, 1, 1); // ブロック数 dim3 grid(BLOCKS_NUM, BLOCKS_NUM, 1); // タイマー処理、msで出ます。 unsigned int hTimer; CUT_SAFE_CALL( cutCreateTimer(&hTimer) ); CUT_SAFE_CALL( cutResetTimer(hTimer) ); CUT_SAFE_CALL( cutStartTimer(hTimer) ); // GPUにジョブを投げます test<<< grid, threads >>>(d_sd3, d_sd1, d_sd2, 0, nSize / 2); // 終了待ち CUDA_SAFE_CALL( cudaThreadSynchronize() ); CUT_SAFE_CALL( cutStopTimer(hTimer) ); double gpuTime = cutGetTimerValue(hTimer); // GPUの処理に問題が起きていないかの確認 // メモリ領域をオーバーするなどkernel.cuが実行できなかった場合エラーになる CUT_CHECK_ERROR("Kernel execution failed"); // 演算結果の取得 CUDA_SAFE_CALL(cudaMemcpy(h_sd3, d_sd3, n / 2, cudaMemcpyDeviceToHost)); FILE fp; if((fp = fopen("log.txt", "w")) == NULL) { exit(-1); } for(i=0; i<nSize / 2; i++) { for(int j = 0; j < 4; j++){ for(int k = 0; k < 4; k++) { fprintf(fp, "%d, %d, %d\n", i, j, k); fprintf(fp, "x1 = %f, y1 = %f, z1 = %f, w = %f\n", h_sd1[i].col[j].x, h_sd1[i].col[j].y, h_sd1[i].col[j].z, h_sd1[i].col[j].w); fprintf(fp, "x2 = %f, y2 = %f, z2 = %f, w = %f\n", h_sd2[i].row[k].x, h_sd2[i].row[k].y, h_sd2[i].row[k].z, h_sd2[i].row[k].w); switch(k){ case 0: fprintf(fp, "x = %f\n",h_sd3[i].col[j].x); break; case 1: fprintf(fp, "y = %f\n", h_sd3[i].col[j].y); break; case 2: fprintf(fp, "z = %f\n",h_sd3[i].col[j].z); break; case 3: fprintf(fp, "w = %f\n",h_sd3[i].col[j].w); break; } } // k } // j fprintf(fp, "threadIdx.x = %d, blockIdx.x = %d\n", h_sd3[i].threadx, h_sd3[i].blockx); } // i printf("Time: %f ms\n", gpuTime); fclose(fp); // クリーンアップ free(h_sd1); free(h_sd2); free(h_sd3); CUDA_SAFE_CALL(cudaFree(d_sd1)); CUDA_SAFE_CALL(cudaFree(d_sd2)); CUDA_SAFE_CALL(cudaFree(d_sd3)); } int main(int argc, char** argv) { Run(argc, argv); // 終了処理 CUT_EXIT(argc, argv); } |
次にGPU内部処理(kernel.cu)です。
#include <math.h> struct Test { float4 col[4], row[4]; int threadx, thready, threadz; int blockx, blocky, blockz; int nSize; // 今回使用しません。 int grid; // 今回使用しません。 }; static struct Test Obj; // GPU側の処理 / global・・・CPU側からでもGPU側からでも呼び出せる関数(必ずvoid型) device・・・GPU側からGPUのみ呼び出せる関数(GPUのグローバルメモリに置かれる) host・・・CPU側からCPUのみ呼び出せる関数(特に指定しない場合全てhostになる) test関数(出力メモリ, 入力データ1(乗算される行列), 入力データ2(乗算するデータ), スレッド分割これ以上できなくなった場合などに使用する開始点変数, 終了点変数) / global void test(Test fOut, Test fIn1, TestfIn2, int nStart, int nEnd) { / 並列動作の順序 while(スレッドX < THREADNUMS) { while(ブロックX < BLOCKNUMS) { while(ブロックY < BLOCKNUMS) { // 計算処理 ブロックY++; } ブロックX++; ブロックY = 0; } スレッドX++; ブロックX = 0; } / // スレッドのインデックス(0-3) const unsigned int tid = threadIdx.x; // グリッドXのインデックス(0-299) const unsigned int bidx = blockIdx.x; // グリッドYのインデックス(0-299) const unsigned int bidy = blockIdx.y; int i = bidx; int j = bidy; // 行列積を1要素ずつ計算します、tid 行 i 列の計算です switch(j) { case 0: fOut[tid].col[i].x = fIn1[tid].col[i].x * fIn2[tid].row[j].x + fIn1[tid].col[i].y * fIn2[tid].row[j].y + fIn1[tid].col[i].z * fIn2[tid].row[j].z + fIn1[tid].col[i].w * fIn2[tid].row[j].w; break; case 1: fOut[tid].col[i].y = fIn1[tid].col[i].x * fIn2[tid].row[j].x + fIn1[tid].col[i].y * fIn2[tid].row[j].y + fIn1[tid].col[i].z * fIn2[tid].row[j].z + fIn1[tid].col[i].w * fIn2[tid].row[j].w; break; case 2: fOut[tid].col[i].z = fIn1[tid].col[i].x * fIn2[tid].row[j].x + fIn1[tid].col[i].y * fIn2[tid].row[j].y + fIn1[tid].col[i].z * fIn2[tid].row[j].z + fIn1[tid].col[i].w * fIn2[tid].row[j].w; break; case 3: fOut[tid].col[i].w = fIn1[tid].col[i].x * fIn2[tid].row[j].x + fIn1[tid].col[i].y * fIn2[tid].row[j].y + fIn1[tid].col[i].z * fIn2[tid].row[j].z + fIn1[tid].col[i].w * fIn2[tid].row[j].w; break; } / 初期化用です。 fOut[tid].col[i].x = 0; fOut[tid].col[i].y = 0; fOut[tid].col[i].z = 0; fOut[tid].col[i].w = 0; / // 最終的に何行何列にいるのか、を出力メモリに書き出します fOut[tid].threadx = tid; fOut[tid].thready = threadIdx.y; fOut[tid].threadz = threadIdx.z; fOut[tid].blockx = bidx; fOut[tid].blocky = bidy; fOut[tid].blockz = blockIdx.z; } |
結果の一部を抜き出しチェックしてみました。
x1, y1, z1, wが行列ABのAのカラム、x2, y2, z2, wがBのロウです。
x1 = 3.000000, y1 = 3.000000, z1 = 6.000000, w = 1.000000 x2 = 5.000000, y2 = 3.000000, z2 = 2.000000, w = 6.000000 w = 42.000000 299, 3, 0 <- スレッドID, ブロックx, ブロックy x1 = 3.000000, y1 = 6.000000, z1 = 7.000000, w = 7.000000 x2 = 4.000000, y2 = 5.000000, z2 = 8.000000, w = 4.000000 x = 126.000000 299, 3, 1 x1 = 3.000000, y1 = 6.000000, z1 = 7.000000, w = 7.000000 x2 = 1.000000, y2 = 0.000000, z2 = 9.000000, w = 6.000000 y = 108.000000 299, 3, 2 x1 = 3.000000, y1 = 6.000000, z1 = 7.000000, w = 7.000000 x2 = 8.000000, y2 = 4.000000, z2 = 8.000000, w = 2.000000 z = 118.000000 299, 3, 3 x1 = 3.000000, y1 = 6.000000, z1 = 7.000000, w = 7.000000 x2 = 5.000000, y2 = 3.000000, z2 = 2.000000, w = 6.000000 w = 89.000000 threadIdx.x = 299, blockIdx.x = 0 |