Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

2015年度先端GPGPUシミュレーション工学特論 第15回 CPUとGPUの協調

365 views

Published on

長岡技術科学大学
2015年度先端GPGPUシミュレーション工学特論(全15回,大学院生対象講義)
第15回CPUとGPUの協調

2015年度先端GPGPUシミュレーション工学特論
・第1回 先端シミュレーションおよび産業界におけるGPUの役割
http://www.slideshare.net/ssuserf87701/2015gpgpu1-59180313
・第1回補足 GROUSEの利用方法
http://www.slideshare.net/ssuserf87701/2015gpgpu1-59180326
・第2回 GPUによる並列計算の概念とメモリアクセス
http://www.slideshare.net/ssuserf87701/2015gpgpu2-59180382
・第3回 GPUプログラム構造の詳細(threadとwarp)
http://www.slideshare.net/ssuserf87701/2015gpgpu3-59180483
・第4回 GPUのメモリ階層の詳細(共有メモリ)
http://www.slideshare.net/ssuserf87701/2015gpgpu4-59180572
・第5回 GPUのメモリ階層の詳細(様々なメモリの利用)
http://www.slideshare.net/ssuserf87701/2015gpgpu5-59180652
・第6回 プログラムの性能評価指針(Flop/Byte,計算律速,メモリ律速)
http://www.slideshare.net/ssuserf87701/2015gpgpu6-59180736
・第7回 総和計算(Atomic演算)
http://www.slideshare.net/ssuserf87701/2015gpgpu7-59180844
・第8回 偏微分方程式の差分計算(拡散方程式)
http://www.slideshare.net/ssuserf87701/2015gpgpu8-59180918
・第9回 偏微分方程式の差分計算(移流方程式)
http://www.slideshare.net/ssuserf87701/2015gpgpu9-59180982
・第10回 Poisson方程式の求解(線形連立一次方程式)
http://www.slideshare.net/ssuserf87701/2015gpgpu10-59181031
・第11回 数値流体力学への応用(支配方程式,CPUプログラム)
http://www.slideshare.net/ssuserf87701/2015gpgpu11-59181134
・第12回 数値流体力学への応用(GPUへの移植)
http://www.slideshare.net/ssuserf87701/2015gpgpu12-59181230
・第13回 数値流体力学への応用(高度な最適化)
http://www.slideshare.net/ssuserf87701/2015gpgpu13-59181316
・第14回 複数GPUの利用
http://www.slideshare.net/ssuserf87701/2015gpgpu14-59181367
・第15回 CPUとGPUの協調
http://www.slideshare.net/ssuserf87701/2015gpgpu15-59181450

2015年度GPGPU実践基礎工学
・第1回 学際的分野における先端シミュレーション技術の歴史
http://www.slideshare.net/ssuserf87701/2015gpgpu1

2015年度GPGPU実践プログラミング
・第1回 GPGPUの歴史と応用例
http://www.slideshare.net/ssuserf87701/2015gpgpu1-59179080

講義には長岡技術科学大学のGPGPUシステム(GROUSE)を利用しています。
開発環境
CPU Intel Xeon X5670 × 32
GPU NVIDIA Tesla M2050(Fermi世代) × 64
CUDA 4.0(諸般の事情によりバージョンアップされていません)
PGI Fortran 11.3

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

2015年度先端GPGPUシミュレーション工学特論 第15回 CPUとGPUの協調

  1. 1. 第15回 CPUとGPUの協調 長岡技術科学大学 電気電子情報工学専攻 出川智啓
  2. 2. 今回の内容 2015/07/23先端GPGPUシミュレーション工学特論2  並列処理と並行処理  ストリーム  複数ストリームを用いたベクトル和の計算  CPUとGPUを用いたベクトル和の計算
  3. 3. 並列処理と並行処理 2015/07/23先端GPGPUシミュレーション工学特論3  並列処理(Parallel Processing)  一つの処理を複数の処理に分割  協調しながら複数の処理を実行  プログラムを高速化するために行われる  並行処理(Concurrent Processing)  複数の処理を実行  複数の処理は必ずしも協調していない  利便性の向上のために行われる  OSが複数のプログラムを実行する等
  4. 4. CUDAプログラムの実行  実行時の流れ(CPU視点)  利用するGPUの初期化やデータの転送などを実行  GPUで実行する関数を呼び出し  GPUから結果を取得 初期化の指示 初期化 カーネルの実行指示 カーネルを実行 結果の取得 実行結果をコピー time CPUとGPUは非同期 CPUは別の処理を実行可能 2015/07/23先端GPGPUシミュレーション工学特論4 必要なデータのコピー メモリに書込
  5. 5. GPUとCPUの並行実行 2015/07/23先端GPGPUシミュレーション工学特論5  GPUのみの処理 float *a = (float *)malloc(NBytes); float *dev_a; cudaMalloc((void **)&dev_a, NBytes); cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice); kernel<<<NB, NT>>>(dev_a); cudaMemcpy(a, dev_a, NBytes, cudaMemcpyDeviceToHost); aを転送(H2D) kernel dev_aを転送(D2H)GPU 待機 待機CPU cudaMemcpy実行 カーネル起動 cudaMemcpy実行 GPUとは同期せず(カーネルの終了を待たず), 直ちに次の処理を実行
  6. 6. GPUとCPUの並行実行 2015/07/23先端GPGPUシミュレーション工学特論6  GPUとCPUの非同期処理 float *a = (float *)malloc(NBytes), *dev_a; cudaMalloc((void **)&dev_a, NBytes); cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice); kernel<<<NB, NT>>>(dev_a);  do_something(a); cudaMemcpy(a, dev_a, NBytes, cudaMemcpyDeviceToHost); CPUとGPUは非同期で処理を実行 aを転送(H2D) kernel 待機 dev_aを転送(D2H)GPU 待機 do_something() 待機CPU cudaMemcpy実行 カーネル起動 cudaMemcpy実行
  7. 7. GPUとCPUの並行実行 2015/07/23先端GPGPUシミュレーション工学特論7  GPUとCPUが非同期で行う処理とは  cudaMemcpy  同期通信(synchronous)  同期が完了するまでCPUとGPUが他の処理を実行しない  ブロッキング(blocking)型とも呼ばれる  カーネル実行  非同期で実行(asynchronous)  カーネルを実行した直後にCPUが他の処理を実行可能
  8. 8. GPUの並行実行(要望) 2015/07/23先端GPGPUシミュレーション工学特論8  通信(データ転送)を同時に行いたい  通信(データ転送)している間に計算を実行したい  負荷の軽い計算を複数同時に実行したい float *a, *b, *dev_a, *dev_b; a = (float *)malloc(NBytes); b = (float *)malloc(NBytes); cudaMalloc((void **)&dev_a, NBytes); cudaMalloc((void **)&dev_b, NBytes); cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice); cudaMemcpy(dev_b, b, NBytes, cudaMemcpyHostToDevice); kernel1<<<1, NT>>>(dev_a); kernel2<<<1, NT>>>(dev_b); cudaMemcpy(a, dev_a, NBytes, cudaMemcpyDeviceToHost); cudaMemcpy(b, dev_b, NBytes, cudaMemcpyDeviceToHost); 転送が終わると直ち にカーネル実行 負荷が軽いカーネル は同時に実行 カーネルが終わると 直ちに転送
  9. 9. GPUの並行実行(要望) 2015/07/23先端GPGPUシミュレーション工学特論9 cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice); cudaMemcpy(dev_b, b, NBytes, cudaMemcpyHostToDevice); kernel1<<<1, NT>>>(dev_a); kernel2<<<1, NT>>>(dev_b); cudaMemcpy(a, dev_a, NBytes, cudaMemcpyDeviceToHost); cudaMemcpy(b, dev_b, NBytes, cudaMemcpyDeviceToHost); 転送が終わると直ち にカーネル実行 負荷が軽いカーネル は同時に実行 カーネルが終わると 直ちに転送 aを転送(H2D) kernel1 dev_aを転送(D2H) bを転送(H2D) kernel2 dev_bを転送(D2H) 転送の 並行実行 転送と カーネ ルの並 行実行 カーネルの 並行実行 転送とカーネル の並行実行 転送の 並行実行
  10. 10. GPUの並行実行(要望) 2015/07/23先端GPGPUシミュレーション工学特論10 cudaMemcpy(dev_a, a, NBytes, cudaMemcpyHostToDevice); cudaMemcpy(dev_b, b, NBytes, cudaMemcpyHostToDevice); kernel1<<<1, NT>>>(dev_a); kernel2<<<1, NT>>>(dev_b); cudaMemcpy(a, dev_a, NBytes, cudaMemcpyDeviceToHost); cudaMemcpy(b, dev_b, NBytes, cudaMemcpyDeviceToHost); 転送が終わると直ち にカーネル実行 負荷が軽いカーネル は同時に実行 カーネルが終わると 直ちに転送 aを転送(H2D) kernel1 dev_aを転送(D2H) bを転送(H2D) kernel2 dev_bを転送(D2H) 転送の 並行実行 転送と カーネ ルの並 行実行 カーネルの 並行実行 転送とカーネル の並行実行 転送の 並行実行 転送の方向が異 なる場合は可能 転送の方向が異 なる場合は可能
  11. 11. Concurrent Kernel Execution 2015/07/23先端GPGPUシミュレーション工学特論11  GPUが並行に処理を実行可能かの確認  cudaGetDeviceProperties()のメンバdeviceOverlap を確認  1なら並行実行可能 int dev = 0; cudaDeviceProp deviceProp; cudaGetDeviceProperties(&deviceProp, dev); if(deviceProp.deviceOverlap == 1) printf("Device %d: ¥"%s¥" supports concurrent kernel execution¥n", dev, deviceProp.name); concurrent.cu
  12. 12. GPUの並行実行 2015/07/23先端GPGPUシミュレーション工学特論12  データ通信とカーネル実行を並行に実行可能  データ通信と並行に実行できるカーネルは一つ  CPUからGPUへの転送,GPUからCPUへの転送,カーネル実 行を並行に実行可能  Compute Capability 2.0以上のGPU  pgaccelinfoのDevice Revision Numberと同じ  複数のカーネルを並行に実行  並行実行可能なカーネルの数  32 Compute Capability 3.5以上  16 Compute Capability 2.0以上3.5未満  並行実行するカーネルが資源を利用できることが条件
  13. 13. ストリーム 2015/07/23先端GPGPUシミュレーション工学特論13  GPUで実行される処理の流れ  同じストリームに属する処理は,必ず命令発行され た順に実行される  異なるストリームに属する処理は並行に実行できる  異なるストリームに属する処理間に依存性がないと仮定  ストリームを複数作ることで処理を並行に実行
  14. 14. 標準のストリーム(ストリーム0) 2015/07/23先端GPGPUシミュレーション工学特論14  ストリームを指定しない場合はストリーム0に所属  ストリーム0は他の処理とは同時に実行されない  ストリーム0はCPUと同期して実行  例外はカーネル実行,明示的な非同期通信等  並行に実行するためには,0でない複数のストリーム に所属させる必要がある
  15. 15. float *a, *b, *dev_a, *dev_b; cudaStream_t stream1, stream2; cudaStreamCreate(&stream1); cudaStreamCreate(&stream2); cudaMalloc((void **)&dev_a, NBytes); cudaMalloc((void **)&dev_b, NBytes); cudaHostAlloc((void **)&a, NBytes, cudaHostAllocDefault); cudaHostAlloc((void **)&b, NBytes, cudaHostAllocDefault); cudaMemcpyAsync(dev_a, a, Nbytes, cudaMemcpyHostToDevice, stream1); cudaMemcpyAsync(dev_b, b, Nbytes, cudaMemcpyHostToDevice, stream2); kernel1<<<1, NT, 0, stream1>>>(dev_a); kernel2<<<1, NT, 0, stream2>>>(dev_b); cudaMemcpyAsync(a, dev_a, Nbytes, cudaMemcpyDeviceToHost, stream1); cudaMemcpyAsync(a, dev_a, Nbytes, cudaMemcpyDeviceToHost, stream2); cudaDeviceSynchronize(); ストリームを用いた並行実行 2015/07/23先端GPGPUシミュレーション工学特論15
  16. 16. ストリームを用いた並行実行 2015/07/23先端GPGPUシミュレーション工学特論16  ストリームの作成(具体的に番号を付けるわけではない)  cudaStreamCreate(cudaStream_t *);  ストリームの破棄  cudaStreamDestroy(cudaStream_t); cudaStream_t stream1, stream2,stream[10]; cudaStreamCreate(&stream1); //個別の変数でストリームを管理することが可能 cudaStreamCreate(&stream2); // for(int i=0;i<10;i++) cudaStreamCreate(&stream[i]);//配列でストリームを管理することも可能 cudaStreamDestroy(stream1);  cudaStreamDestroy(stream2);  for(int i=0;i<10;i++) cudaStreamDestroy(stream[i]);
  17. 17. ストリームを用いた並行実行 2015/07/23先端GPGPUシミュレーション工学特論17  cudaMemcpyAsync  非同期転送(asynchronous)  5番目の引数としてストリームを指定  CPUのメモリには必ずページロックメモリを指定  cudaHostAlloc()もしくはcudaMallocHost()で確保  カーネル実行  <<<>>>内の4番目にストリームを指定  (1)ブロック数 (2)1ブロックあたりのスレッド数 (3)共有メモリサイズ (4)ストリーム  共有メモリのサイズを指定しない場合は0を記述
  18. 18. 並行実行の同期 2015/07/23先端GPGPUシミュレーション工学特論18  cudaDeviceSynchronize()  GPUで実行している全ての処理が完了するまで待機  cudaStreamSynchronize(ストリーム)  引数で指定したストリームに属する命令が完了するまで待機 … cudaMemcpyAsync(a, dev_a, Nbytes, cudaMemcpyDeviceToHost, stream1); cudaMemcpyAsync(b, dev_b, Nbytes, cudaMemcpyDeviceToHost, stream2); cudaDeviceSynchronize(); //dev_a,dev_b両方の転送が終わるまで待機 … cudaMemcpyAsync(a, dev_a, Nbytes, cudaMemcpyDeviceToHost, stream1); cudaMemcpyAsync(b, dev_b, Nbytes, cudaMemcpyDeviceToHost, stream2); cudaStreamSynchronize(stream1); //dev_aの転送が終わると次の処理へ移行
  19. 19. ベクトル和C=A+Bの並行実行 2015/07/23先端GPGPUシミュレーション工学特論19  並行実行の方針  複数のストリームを作成  配列のまとまったサイズを異なるストリームに所属  A, Bの初期化はCPUで実行し,GPUへ転送  A, Bの転送が終わったストリームから足し算を実行し, CPUへCを転送
  20. 20. ベクトル和C=A+Bの並行実行 2015/07/23先端GPGPUシミュレーション工学特論20  同期実行  並行実行  処理をオーバーラップできれば若干高速化 Aを転送(H2D) Bを転送(H2D) add dev_Cを転送(D2H) A(H2D) B(H2D) add dev_C(D2H) A(H2D) B(H2D) add dev_C(D2H) A(H2D) B(H2D) add dev_C(D2H) Stream 0 Stream 1 Stream 2 Stream 3 高速化 実行開始 処理時間
  21. 21. #include<stdio.h> #include<stdlib.h> #include<omp.h> #define N (1024*1024*8) #define Nbytes (N*sizeof(float)) #define NT 256 #define NB (N/NT) //Streamの数=並行実行数 #define Stream 4 //カーネルは変更なし __global__ void init (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; } //カーネルは変更なし __global__ void add (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; c[i] = a[i] + b[i]; } ベクトル和(ストリームの利用) 2015/07/23先端GPGPUシミュレーション工学特論21 vectoradd_stream.cu
  22. 22. int main(){ float *a,*b,*c; int stm; cudaStream_t stream[Stream]; //ページロックホストメモリを確保 float *host_a, *host_b, *host_c; cudaHostAlloc((void **)&host_c, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_a, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_b, Nbytes, cudaHostAllocDefault); for(int i=0;i<N;i++){ host_a[i] = 1.0f; host_b[i] = 2.0f; host_c[i] = 0; } for(stm=0;stm<Stream;stm++){ cudaStreamCreate(&stream[stm]); } cudaMalloc( (void **)&a, Nbytes); cudaMalloc( (void **)&b, Nbytes); cudaMalloc( (void **)&c, Nbytes); ベクトル和(ストリームの利用) 2015/07/23先端GPGPUシミュレーション工学特論22 vectoradd_stream.cu
  23. 23. double time_start = omp_get_wtime(); for(stm=0;stm<Stream;stm++){ int idx = stm*N/Stream; cudaMemcpyAsync(&a[idx], &host_a[idx], Nbytes/Stream, cudaMemcpyHostToDevice,  stream[stm]); cudaMemcpyAsync(&b[idx], &host_b[idx], Nbytes/Stream, cudaMemcpyHostToDevice,  stream[stm]); add<<< NB/Stream, NT, 0 ,stream[stm]>>>(&a[idx],&b[idx],&c[idx]); cudaMemcpyAsync(&host_c[idx], &c[idx], Nbytes/Stream, cudaMemcpyDeviceToHost,  stream[stm]); } cudaDeviceSynchronize(); double time_end = omp_get_wtime(); ベクトル和(ストリームの利用) 2015/07/23先端GPGPUシミュレーション工学特論23 vectoradd_stream.cu
  24. 24. double sum=0; for(int i=0;i<N;i++)sum+=host_c[i]; printf("%f¥n",sum/N); printf("elapsed time = %f sec¥n", time_end‐time_start); cudaFreeHost(host_a); cudaFreeHost(host_b); cudaFreeHost(host_c); cudaFree(a); cudaFree(b); cudaFree(c); for(stm=0;stm<Stream;stm++){ cudaStreamDestroy(stream[stm]); } return 0; } ベクトル和(ストリームの利用) 2015/07/23先端GPGPUシミュレーション工学特論24 vectoradd_stream.cu
  25. 25. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論25  配列の要素数 N=223  1ブロックあたりのスレッド数 256  OpenMPの関数を用いて実行時間を測定  コンパイルにはオプション‐Xcompiler ‐fopenmpが必要 ストリーム数 実行時間[ms] 1 17.5 2 15.5 4 14.5 8 14.1 16 14.0 ストリーム数 実行時間[ms]
  26. 26. プロファイラによる確認 2015/07/23先端GPGPUシミュレーション工学特論26  ストリーム数4  メモリ転送とベクトル和を4セット実行 # CUDA_PROFILE_LOG_VERSION 2.0 # CUDA_DEVICE 0 Tesla M2050 # TIMESTAMPFACTOR fffff60a9ac44950 timestamp,method,gputime,cputime,occupancy timestamp=[ 92925.000 ] method=[ memcpyHtoDasync ] gputime=[ 1403.488 ] cputime=[ 16.000 ] timestamp=[ 92948.000 ] method=[ memcpyHtoDasync ] gputime=[ 1410.880 ] cputime=[ 5.000 ] timestamp=[ 92977.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 253.472 ] cputime=[ 23.000 ] occupancy=[ 1.000 ] timestamp=[ 93005.000 ] method=[ memcpyDtoHasync ] gputime=[ 1520.448 ] cputime=[ 6.000 ] timestamp=[ 93014.000 ] method=[ memcpyHtoDasync ] gputime=[ 1931.936 ] cputime=[ 6.000 ] timestamp=[ 93021.000 ] method=[ memcpyHtoDasync ] gputime=[ 1414.272 ] cputime=[ 5.000 ] timestamp=[ 93029.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 253.248 ] cputime=[ 8.000 ] occupancy=[ 1.000 ] timestamp=[ 93038.000 ] method=[ memcpyDtoHasync ] gputime=[ 1521.216 ] cputime=[ 5.000 ] timestamp=[ 93045.000 ] method=[ memcpyHtoDasync ] gputime=[ 1935.456 ] cputime=[ 6.000 ] timestamp=[ 93053.000 ] method=[ memcpyHtoDasync ] gputime=[ 1417.152 ] cputime=[ 6.000 ] timestamp=[ 93060.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 252.352 ] cputime=[ 7.000 ] occupancy=[ 1.000 ] timestamp=[ 93068.000 ] method=[ memcpyDtoHasync ] gputime=[ 1566.624 ] cputime=[ 6.000 ] timestamp=[ 93076.000 ] method=[ memcpyHtoDasync ] gputime=[ 1978.208 ] cputime=[ 6.000 ] timestamp=[ 93083.000 ] method=[ memcpyHtoDasync ] gputime=[ 1414.528 ] cputime=[ 6.000 ] timestamp=[ 93090.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 225.568 ] cputime=[ 8.000 ] occupancy=[ 1.000 ] timestamp=[ 93099.000 ] method=[ memcpyDtoHasync ] gputime=[ 1319.040 ] cputime=[ 6.000 ]
  27. 27. Nsightによる確認 2015/07/23先端GPGPUシミュレーション工学特論27  ストリーム数8  カーネル実行と転送(CPUからGPU,GPUからCPU)がオー バーラップ
  28. 28. CPUとGPUの協調 2015/07/23先端GPGPUシミュレーション工学特論28  cudaDeviceSynchronize()が呼ばれるまでCPUと GPUは非同期  GPUでベクトル和を計算している間,CPUは待機  処理の一部をCPUが実行する事で高速化が可能  GPUのカーネル,非同期転送処理を全て呼び終わった後に CPUでもベクトル和を計算 A(H2D) B(H2D) add dev_C(D2H) A(H2D) B(H2D) add dev_C(D2H) 転送・カー ネル呼出 add Stream 1 Stream 2 CPU
  29. 29. #include<stdio.h> #include<stdlib.h> #include<omp.h> #define N (1024*1024*8) #define Nbytes (N*sizeof(float)) #define NT 256 #define NB (N/NT) //Streamの数=並行実行数 #define Stream 4 //CPUのStream分担数 //GPUはStream‐CPU分の数だけ並行処理を実行 #define CPU 1 //カーネルは変更なし __global__ void init (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; } //カーネルは変更なし __global__ void add (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; c[i] = a[i] + b[i]; } ベクトル和(CPU・GPU協調) 2015/07/23先端GPGPUシミュレーション工学特論29 vectoradd_coop.cu
  30. 30. int main(){ float *a,*b,*c; int stm; cudaStream_t stream[Stream]; float *host_a, *host_b, *host_c; cudaHostAlloc((void **)&host_c, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_a, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_b, Nbytes, cudaHostAllocDefault); for(int i=0;i<N;i++){ host_a[i] = 1.0f; host_b[i] = 2.0f; host_c[i] = 0; } for(stm=0;stm<Stream‐CPU;stm++){ cudaStreamCreate(&stream[stm]); } cudaMalloc( (void **)&a, Nbytes); cudaMalloc( (void **)&b, Nbytes); cudaMalloc( (void **)&c, Nbytes); ベクトル和(CPU・GPU協調) 2015/07/23先端GPGPUシミュレーション工学特論30 vectoradd_coop.cu
  31. 31. double time_start = omp_get_wtime(); for(stm=0;stm<Stream‐CPU;stm++){ int idx = stm*N/Stream; cudaMemcpyAsync(&a[idx], &host_a[idx], Nbytes/Stream, cudaMemcpyHostToDevice,  stream[stm]); cudaMemcpyAsync(&b[idx], &host_b[idx], Nbytes/Stream, cudaMemcpyHostToDevice,  stream[stm]); add<<< NB/Stream, NT, 0 ,stream[stm]>>>(&a[idx],&b[idx],&c[idx]); cudaMemcpyAsync(&host_c[idx], &c[idx], Nbytes/Stream, cudaMemcpyDeviceToHost,  stream[stm]); } for(int i=(Stream‐CPU)*N/Stream;i<N;i++) host_c[i] = host_a[i] + host_b[i]; cudaDeviceSynchronize(); double time_end = omp_get_wtime(); ベクトル和(CPU・GPU協調) 2015/07/23先端GPGPUシミュレーション工学特論31 vectoradd_coop.cu
  32. 32. double sum=0; for(int i=0;i<N;i++)sum+=host_c[i]; printf("%f¥n",sum/N); printf("elapsed time = %f sec¥n", time_end‐time_start); cudaFreeHost(host_a); cudaFreeHost(host_b); cudaFreeHost(host_c); cudaFree(a); cudaFree(b); cudaFree(c); for(stm=0;stm<Stream‐CPU;stm++){ cudaStreamDestroy(stream[stm]); } return 0; } ベクトル和(CPU・GPU協調) 2015/07/23先端GPGPUシミュレーション工学特論32 vectoradd_coop.cu
  33. 33. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論33  配列の要素数 N=223  1ブロックあたりのスレッド数 256  ストリームの数,CPUの負荷割合を変えて計算  CPU負荷割合=CPUが計算するストリーム数/ストリーム数  0のとき全てGPUで計算,1のとき全てCPUで計算  OpenMPの関数を用いて実行時間を測定  コンパイルにはオプション‐Xcompiler ‐fopenmpが必要
  34. 34. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論34 ストリーム 数 CPU 負荷割合 実行時間 [ms] 1 0 (GPU) 17.6 1 (CPU) 36.4 2 0.0 15.5 0.5 18.3 1.0 36.4 4 0.00 14.5 0.25 11.2 0.50 18.3 0.75 27.4 1.00 36.3 ストリーム 数 CPU 負荷割合 実行時間 [ms] 8 0.000 14.1 0.125 12.5 0.250 10.8 0.375 13.8 0.500 18.3 0.625 22.9 0.750 27.4 0.875 31.9 1.000 36.3
  35. 35. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論35 ストリーム 数 CPU 負荷割合 実行時間 [ms] 16 0.0000 14.0 0.0625 13.1 0.1250 12.2 0.1875 11.5 0.2500 10.6 0.3125 11.6 0.3750 13.9 0.4375 16.2 0.5000 18.4 ストリーム 数 CPU 負荷割合 実行時間 [ms] 16 0.5625 20.6 0.6250 22.9 0.6875 25.1 0.7500 27.5 0.8125 29.6 0.8750 31.9 0.9375 34.3 1.0000 36.3 grouseではCPUが全体の処理の1/4を処理すると最も効率が良い
  36. 36. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論36 CPU負荷割合 実行時間[ms] ストリーム数
  37. 37. OpenMPによる処理の効率化 2015/07/23先端GPGPUシミュレーション工学特論37  CPUとGPUの協調版  1スレッドが転送やカーネルを呼び出し  全て呼び出した後にベクトル和を実行  複数のスレッドを起動  1スレッドをカーネル起動,非同期転送呼出に充てる  CPUではベクトル和を並列に計算
  38. 38. OpenMP 2015/07/23先端GPGPUシミュレーション工学特論38  並列に処理を実行させる箇所に指示句(ディレクティ ブ)を挿入  for文の並列化  ディレクティブを一行追加(#pragma omp ~) #pragma omp parallel for for(int i=0; i<N; i++) C[i] = A[i] + B[i]
  39. 39. #include<stdio.h> #include<stdlib.h> #define N (1024*1024) #define Nbytes (N*sizeof(float)) int main(){ float *a,*b,*c; int i; a = (float *)malloc(Nbytes); b = (float *)malloc(Nbytes); c = (float *)malloc(Nbytes); for(i=0; i<N; i++){ a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; } for(i=0; i<N; i++) c[i] = a[i] + b[i]; for(i=0; i<N; i++) printf("%f+%f=%f¥n", a[i],b[i],c[i]); return 0; } 逐次(並列化前)プログラム 2015/07/23先端GPGPUシミュレーション工学特論39
  40. 40. 並列化プログラム 2015/07/23先端GPGPUシミュレーション工学特論40 #include<stdio.h> #include<stdlib.h> #define N (1024*1024) #define Nbytes (N*sizeof(float)) int main(){ float *a,*b,*c; int i; a = (float *)malloc(Nbytes); b = (float *)malloc(Nbytes); c = (float *)malloc(Nbytes); #pragma omp parallel { #pragma omp for for(i=0; i<N; i++){ a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; } #pragma omp for for(i=0; i<N; i++) c[i] = a[i] + b[i]; } for(i=0; i<N; i++) printf("%f+%f=%f¥n", a[i],b[i],c[i]); return 0; }
  41. 41. OpenMPの指示文 2015/07/23先端GPGPUシミュレーション工学特論41  並列処理制御  OpenMPで並列処理を行う領域の定義  並列実行領域(Parallel Region)構文  ワークシェアリング(Work sharing)構文  同期制御  OpenMP並列領域内でのデータアクセス,命令実行の同期  データ属性制御  並列領域内で利用されるデータの属性を定義  その他
  42. 42. 並列実行領域(Parallel Region)構文 2015/07/23先端GPGPUシミュレーション工学特論42  parallel構文  parallel構文で指示された領域では指定されたスレッド が並列に処理を実行  全てのスレッドが同じ処理を実行 #pragma omp parallel //{ <‐ここに括弧を書くとエラー { 複数のスレッドが起動され,ここに書いてある処理を実行 全てのスレッドが同じ処理を実行 }
  43. 43. ワークシェアリング(Work sharing)構文 2015/07/23先端GPGPUシミュレーション工学特論43  for構文  parallel構文で指定された並列実行領域内で利用  直後のforループを各スレッドに分割して並列処理を実行  for(初期化;継続条件;再初期化)で構成されるforルー プが対象  全てのスレッドが処理を終了するまで他のスレッドは待機 #pragma omp parallel { #pragma omp for for(i=0; i<N; i++){ forループを自動的に分割して各スレッドが実行 } 全てのスレッドが処理を終了するまで待機 }
  44. 44. single構文 2015/07/23先端GPGPUシミュレーション工学特論44  parallel構文で指定された並列実行領域内で利用  一つのスレッドのみが処理を実行  処理を終了するまで他のスレッドは待機  待機させる必要がない場合はnowait節を指定 #pragma omp parallel { #pragma omp single { 1スレッドのみが処理を実行 他のスレッドは待機 } single構文内のスレッドが処理を終了するまで待機 }
  45. 45. nowait指示節 2015/07/23先端GPGPUシミュレーション工学特論45  ワークシェア構文で指定されたブロックの最後で同 期せず,処理を継続  全スレッドが処理を終了するまで待たず,次の処理を実行 #pragma omp parallel { #pragma omp single nowait { 1スレッドのみが処理を実行 他のスレッドは待機せず,以降の処理を実行 } ... }
  46. 46. #include<stdio.h> #include<stdlib.h> #include<omp.h> #define N (1024*1024*8) #define Nbytes (N*sizeof(float)) #define NT 256 #define NB (N/NT) #define Stream 4 #define CPU 2 #define Threads 12 //カーネルは変更なし __global__ void init (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; a[i] = 1.0; b[i] = 2.0; c[i] = 0.0; } //カーネルは変更なし __global__ void add (float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; c[i] = a[i] + b[i]; } ベクトル和(CPUをOpenMPで並列化) 2015/07/23先端GPGPUシミュレーション工学特論46 vectoradd_coop_omp.cu
  47. 47. int main(){ float *a,*b,*c; int stm; cudaStream_t stream[Stream]; float *host_a, *host_b, *host_c; cudaHostAlloc((void **)&host_c, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_a, Nbytes, cudaHostAllocDefault); cudaHostAlloc((void **)&host_b, Nbytes, cudaHostAllocDefault); for(int i=0;i<N;i++){ host_a[i] = 1.0f; host_b[i] = 2.0f; host_c[i] = 0; } for(stm=0;stm<Stream‐CPU;stm++){ cudaStreamCreate(&stream[stm]); } cudaMalloc( (void **)&a, Nbytes); cudaMalloc( (void **)&b, Nbytes); cudaMalloc( (void **)&c, Nbytes); omp_set_num_threads(Threads); double time_start = omp_get_wtime(); #pragma omp parallel { ベクトル和(CPUをOpenMPで並列化) 2015/07/23先端GPGPUシミュレーション工学特論47 vectoradd_coop_omp.cu
  48. 48. #pragma omp single nowait { for(stm=0;stm<Stream‐CPU;stm++){ int idx = stm*N/Stream; cudaMemcpyAsync(&a[idx], &host_a[idx], Nbytes/Stream,   cudaMemcpyHostToDevice,stream[stm]); cudaMemcpyAsync(&b[idx], &host_b[idx], Nbytes/Stream,  cudaMemcpyHostToDevice,stream[stm]); add<<< NB/Stream, NT, 0 ,stream[stm]>>>(&a[idx],&b[idx],&c[idx]); cudaMemcpyAsync(&host_c[idx], &c[idx], Nbytes/Stream,  cudaMemcpyDeviceToHost,stream[stm]); } #pragma omp for for(int i=(Stream‐CPU)*N/Stream;i<N;i++) host_c[i] = host_a[i] + host_b[i]; } } //#pragma omp parallelの終端 ベクトル和(CPUをOpenMPで並列化) 2015/07/23先端GPGPUシミュレーション工学特論48 vectoradd_coop_omp.cu
  49. 49. cudaDeviceSynchronize(); double time_end = omp_get_wtime(); double sum=0; for(int i=0;i<N;i++)sum+=host_c[i]; printf("%f¥n",sum/N); printf("elapsed time = %f sec¥n", time_end‐time_start); cudaFreeHost(host_a); cudaFreeHost(host_b); cudaFreeHost(host_c); cudaFree(a); cudaFree(b); cudaFree(c); for(stm=0;stm<Stream‐CPU;stm++){ cudaStreamDestroy(stream[stm]); } return 0; } ベクトル和(CPUをOpenMPで並列化) 2015/07/23先端GPGPUシミュレーション工学特論49 vectoradd_coop_omp.cu
  50. 50. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論50  配列の要素数 N=223  1ブロックあたりのスレッド数 256  ストリームの数,CPUの負荷割合,CPUスレッド数を 変えて計算  OpenMPの関数を用いて実行時間を測定  コンパイルにはオプション‐Xcompiler ‐fopenmpが必要
  51. 51. 実行結果(CPUスレッドごとに最速となる条件) 2015/07/23先端GPGPUシミュレーション工学特論51  スレッド数が多くなると実行時間が実行毎に変化  1スレッドCPU+GPU協調版のような評価ができない CPUスレッド数 ストリーム数 CPU負荷割合 実行時間[ms] 1 16 0.1875 11.4 2 16 0.3125 9.82 3 16 0.4375 8.32 4 16 0.4375 8.23 5 2 0.5000 10.1 6 16 0.3750 9.18 7 16 0.6875 8.89 8 16 0.6250 8.70 9 4 0.7500 8.87 10 16 0.7500 8.76 11 16 0.5625 10.3 12 16 0.4375 17.8 スレッド数の増加と ともに,CPUの負荷 割合も増加させる と高速化に有効
  52. 52. 実行結果 2015/07/23先端GPGPUシミュレーション工学特論52  CPU 1スレッド 36.4 ms  GPU(1ストリーム) 17.5 ms  GPU(16ストリーム) 14.0 ms  CPUとGPUの協調 10.6 ms  16ストリーム,CPU負荷割合0.25  CPUとGPUの協調 8.23 ms  CPU 4スレッド  16ストリーム,CPU負荷割合0.4375 単一GPU,1ストリームの 2倍程度高速化
  53. 53. 複数GPUでのストリームの利用 2015/07/23先端GPGPUシミュレーション工学特論53  各GPUでストリームを作り,並行実行することが可能  grouseでは  CPUとGPU4台で非同期実行が可能  各GPUに複数のストリームが存在し,処理を並行実行  処理するデータの割当と管理,処理の進行状況の把握が 著しく複雑化  注意点  あるGPUが作成したストリームは他のGPUでは利用できない  cudaSetDeviceでGPUを切り替えてからストリームを作成

×