2019/08/26

forループをCUDAで並列化する方法

背景


CUDAを利用して並列化を行い、処理を高速化する必要が出てきたため、CUDAで並列化処理を記述する方法について記述する

記事の目的


CUDAで並列化処理を行う際のテンプレートを作成する

CUDA


ここでは、CUDAを利用したCプログラムの記述方法について記載する。

CUDAとは

CUDAは、nvidia社が提供するGPUを利用した並列演算プログラミング基盤である

利点

  • ユーザーが多く、情報も入手しやすい
  • ライブラリが充実している
  • 導入が容易である

テンプレート

CUDAを利用して並列処理を行うテンプレートを記載する。
  1. // cuda_sample_code.cu
  2. #include <stdio.h>
  3. // デバイス(GPU)側のスレッドの設定 //////////////////////////////////
  4. // BLOCK_SIZE_Xは、1ブロックあたりのスレッド数を表す
  5. // GPUの種類により、1ブロックあたりのスレッド数の制限が異なる
  6. // 最適なスレッド数を設定しないと、カーネル関数の処理がスキップされる
  7. // 注)上記の場合、エラーでプロセスが落ちる事はない
  8. #define BLOCK_SIZE_X 512
  9. // カーネル関数(GPUで処理する関数)vec_sumの宣言 /////////////////////
  10. // この関数がGPUで並列実行される
  11. __global__
  12. void vec_sum(float k, float *a, float *b, float *c)
  13. {
  14. // iは、"for(int i=0; i < grid.x * block.x; i++)" の値を取る
  15. // ただし、並列処理のため、i++順に処理されるわけではない
  16. int i = blockIdx.x*blockDim.x + threadIdx.x;
  17. c[i] = k*a[i] + b[i];
  18. // GPU内のスレッドの同期 ////////////////////////////////////////
  19. // block_size * grid_size個の全てのスレッドが、ここで同期する
  20. __syncthreads();
  21. }
  22. // カーネル関数を呼び出す関数を宣言 /////////////////////////////////
  23. void cuda_handler(float *a, float *b, float *c)
  24. {
  25. // デバイス用のポインタを宣言 ///////////////////////////////////
  26. float *d_a, *d_b, *d_c;
  27. // デバイス(GPU)側の配列のメモリを確保 //////////////////////////
  28. // デバイス側のメモリ確保処理は重いため、回数は減らした方が良い
  29. cudaMalloc(&d_a, N*sizeof(float));
  30. cudaMalloc(&d_b, N*sizeof(float));
  31. cudaMalloc(&d_c, N*sizeof(float));
  32. // ホスト側の配列内のデータをデバイス側にコピー /////////////////
  33. cudaMemcpy(d_a, a, N*sizeof(float), cudaMemcpyHostToDevice);
  34. cudaMemcpy(d_b, b, N*sizeof(float), cudaMemcpyHostToDevice);
  35. // デバイス側の配列cの全ての要素に0をセット /////////////////////
  36. cudaMemset(d_c, 0, N*sizeof(float));
  37. // cudaを利用した処理のうち、最後のエラーを取得し、表示 /////////
  38. // ここでは、メモリへのデータセットのエラーチェックに利用している
  39. // 注)nvccのコンパイルオプションに"-g -G"を追加しないと動作しない
  40. // 注)エラーチェックをアクティブにすると、性能が極端に落ちる
  41. checkCudaErrors(cudaGetLastError());
  42. // 並列処理するスレッド数を定義 /////////////////////////////////
  43. // 総スレッド数は block_size * grid_size = N 個である
  44. // x, y, zの3次元まで設定可能である
  45. dim3 block_size (BLOCK_SIZE_X, 1, 1);
  46. dim3 grid_size (N / block.x, 1, 1);
  47. // カーネル関数(GPUで処理する関数)の呼び出し ////////////////////
  48. // カーネル関数内では、デバイス側のメモリ内のデータのみ操作可能
  49. vec_sum<<<grid_size, block_size>>>(2.0f, d_a, d_b, d_c);
  50. // cudaを利用した処理のうち、最後のエラーを取得し、表示 /////////
  51. // ここでは、vec_sumのエラーチェックに利用している
  52. // 注)nvccのコンパイルオプションに"-g -G"を追加しないと動作しない
  53. // 注)エラーチェックをアクティブにすると、性能が極端に落ちる
  54. checkCudaErrors(cudaGetLastError());
  55. // この行までに実行されたカーネル関数の処理が完了するまで待機 ///
  56. // デバイスとホストの処理は非同期である
  57. // 同期処理を行うか、cudaMemcpyするまで、互いは独立して動作する
  58. cudaThreadSynchronize();
  59. // 計算結果をホスト側にコピー ///////////////////////////////////
  60. cudaMemcpy(c, d_c, N*sizeof(float), cudaMemcpyDeviceToHost);
  61. // デバイス(GPU)側の配列のメモリを開放 //////////////////////////
  62. cudaFree(d_a);
  63. cudaFree(d_b);
  64. cudaFree(d_c);
  65. // cudaを利用した処理のうち、最後のエラーを取得し、表示 /////////
  66. // ここでは、メモリへのデータセットのエラーチェックに利用している
  67. // 注)nvccのコンパイルオプションに"-g -G"を追加しないと動作しない
  68. // 注)エラーチェックをアクティブにすると、性能が極端に落ちる
  69. checkCudaErrors(cudaGetLastError());
  70. }
  71. int main(void)
  72. {
  73. // 計算回数の設定 ///////////////////////////////////////////////
  74. // N = 512×2048
  75. int N = 1<<20;
  76. // ホスト用のポインタを宣言 /////////////////////////////////////
  77. float *a, *b, *c;
  78. // ホスト側の配列のメモリを確保 /////////////////////////////////
  79. a = (float*)malloc(N*sizeof(float));
  80. b = (float*)malloc(N*sizeof(float));
  81. c = (float*)malloc(N*sizeof(float));
  82. // a, bの配列にそれぞれ1,2を代入////////////////////////////////
  83. for (int i = 0; i < N; i++) {
  84. a[i] = 1.0f;
  85. b[i] = 2.0f;
  86. }
  87. // cudaでの処理を行う関数 ///////////////////////////////////////
  88. cuda_handler(a, b, c);
  89. // 計算結果の確認 ///////////////////////////////////////////////
  90. float maxError = 0.0f;
  91. for (int i = 0; i < N; i++)
  92. {
  93. maxError = max(maxError, abs(c[i]-4.0f));
  94. }
  95. printf("Max error: %f", maxError);
  96. // ホスト側の配列のメモリを開放 /////////////////////////////////
  97. free(a);
  98. free(b);
  99. free(c);
  100. return 0;
  101. }

テンプレートのコンパイル方法

テンプレートのコンパイル方法を記載する。
  1. nvcc -lcuda ./cuda_sample_code.cu -o ./cuda_sample
デバッグログを出力する場合は、下記のように記載する。
  1. nvcc -lcuda -g -G ./cuda_sample_code.cu -o ./cuda_sample

テンプレートの実行結果

テンプレートの実行結果を記載する。
  1. $ ./cuda_sample
  2. Max error: 0.000000

まとめ


  • CUDAでプログラミングをする際のテンプレートを調査、記載した

参考文献



変更履歴


  1. 2019/08/26: 新規作成

0 件のコメント:

コメントを投稿

MQTTの導入

背景 IoTデバイスの接続環境構築のため、MQTT(mosquitto)の導入を行った。 記事の目的 MQTT(mosquitto)をUbuntuに導入する mosquitto ここではmosquittoについて記載する。 MQTT MQTT(Message Qu...