More Related Content Similar to 2015年度先端GPGPUシミュレーション工学特論 第7回 総和計算(Atomic演算)
Similar to 2015年度先端GPGPUシミュレーション工学特論 第7回 総和計算(Atomic演算) (20) 2015年度先端GPGPUシミュレーション工学特論 第7回 総和計算(Atomic演算)7. sum
+ + + +
総和計算の並列化
2015/05/28先端GPGPUシミュレーション工学特論7
GPUでは並列計算が必須
ベクトル和のようにforループを分割
for(i=0;i<N;i++){
sum += idata[i];
}
i=blockIdx.x*blockDim.x+threadIdx.x;
sum += idata[i];
?
スレッド
0
スレッド
2
スレッド
1
スレッド
3
a[i]
22. int main(){
int *idata,*odata; //GPU用変数 idata:入力,odata:出力(=総和)
int *host_idata,sum; //CPU用変数 host_idata:初期化用,sum:総和
cudaMalloc( (void **)&idata, Nbytes);
cudaMalloc( (void **)&odata, sizeof(int));
//CPU側でデータを初期化してGPUへコピー
host_idata = (int *)malloc(Nbytes);
init(host_idata);
cudaMemcpy(idata, host_idata,Nbytes, cudaMemcpyHostToDevice);
free(host_idata);
reduction0<<< NB, NT >>>(idata, odata);
//GPUから総和の結果を受け取って画面表示
cudaMemcpy(&sum, odata, sizeof(int), cudaMemcpyDeviceToHost);
printf("sum = %d¥n", sum);
cudaFree(idata);
cudaFree(odata);
return 0;
}
GPUプログラム(1スレッド実装)
2015/05/28先端GPGPUシミュレーション工学特論22
reduction0.cu
28. プロファイル結果の確認
2015/05/28先端GPGPUシミュレーション工学特論28
標準で出力される値
method カーネルや関数(API)の名称
gputime GPU上で処理に要した時間(s単位)
cputime CPUで処理(=カーネル起動)に要した時間
実際の実行時間=cputime+gputime
occupancy 同時実行されているWarp数と実行可能な最大
Warp数の比(一般的には1に近いほどよい)
$ cat cuda_profile_0.log
# CUDA_PROFILE_LOG_VERSION 2.0
# CUDA_DEVICE 0 Tesla M2050
# TIMESTAMPFACTOR fffff6139ae53e88
method,gputime,cputime,occupancy
method=[ memcpyHtoD ] gputime=[ 2.208 ] cputime=[ 42.000 ]
method=[ _Z10reduction0PiS_ ] gputime=[ 120.704 ] cputime=[ 15.000 ] occupancy=[ 0.021 ]
method=[ memcpyDtoH ] gputime=[ 2.592 ] cputime=[ 164.000 ]
31. int main(){
int *idata,*odata; //GPU用変数 idata:入力,odata:出力(=総和)
int *host_idata,sum; //CPU用変数 host_idata:初期化用,sum:総和
cudaMalloc( (void **)&idata, Nbytes);
cudaMalloc( (void **)&odata, sizeof(int));
//CPU側でデータを初期化してGPUへコピー
host_idata = (int *)malloc(Nbytes);
init(host_idata);
cudaMemcpy(idata, host_idata,Nbytes, cudaMemcpyHostToDevice);
free(host_idata);
sum = 0;//出力用変数odataを0で初期化するためにsumに0を代入して送る
cudaMemcpy(odata, &sum, sizeof(int), cudaMemcpyHostToDevice);
reduction0<<< NB, NT >>>(idata, odata);
//GPUから総和の結果を受け取って画面表示
cudaMemcpy(&sum, odata, sizeof(int), cudaMemcpyDeviceToHost);
printf("sum = %d¥n", sum);
cudaFree(idata);
cudaFree(odata);
return 0;
}
GPUプログラム(atomic演算)
2015/05/28先端GPGPUシミュレーション工学特論31
reduction0atomic.cu
37. int main(){
int *idata,*odata; //GPU用変数 idata:入力,odata:出力(=総和)
int *host_idata,sum; //CPU用変数 host_idata:初期化用,sum:総和
cudaMalloc( (void **)&idata, Nbytes);
cudaMalloc( (void **)&odata, sizeof(int));
//CPU側でデータを初期化してGPUへコピー
host_idata = (int *)malloc(Nbytes);
init(host_idata);
cudaMemcpy(idata, host_idata,Nbytes, cudaMemcpyHostToDevice);
free(host_idata);
reduction1<<< NB, NT >>>(idata, odata);
//GPUから総和の結果を受け取って画面表示
cudaMemcpy(&sum, odata, sizeof(int), cudaMemcpyDeviceToHost);
printf("sum = %d¥n", sum);
cudaFree(idata);
cudaFree(odata);
return 0;
}
GPUプログラム(Ver.1)
2015/05/28先端GPGPUシミュレーション工学特論37
reduction1.cu
38. __global__ void reduction1(int *idata,int *odata){
int i = blockIdx.x*blockDim.x + threadIdx.x;//スレッドと配列要素の対応
int tx = threadIdx.x; //スレッド番号
int step; //Reductionの段数をカウントする変数
int stride; //隣の配列要素までの距離
stride = 1;
for(step=1;step<=STEP;step++){
if(tx%(2*stride)==0){
idata[i] = idata[i]+idata[i+stride];
}
__syncthreads();
stride = stride*2;
}
if(tx==0) odata[0] = idata[0];
}
GPUプログラム(Ver.1)
2015/05/28先端GPGPUシミュレーション工学特論38
reduction1.cu
40. odata[0]
各Stepでの総和計算(Step 2)
2015/05/28先端GPGPUシミュレーション工学特論40
stepを2に進める
strideを2倍(stride=2)
処理を行うスレッドを選択
スレッド番号をstride*2で割った剰余が0
スレッド番号が4の倍数と0のスレッドが「自
身が読み込む配列要素iの値」と「隣
の配列要素の値」を加算
スレッド間の同期をとる
ストライドを2倍して次のstepに備える
+ + + +
+ +
+
Step 1
Step 2
Step 3
stride = 1;
for(step=1;step<=STEP;step++){
if(tx%(stride*2)==0){
idata[i]=idata[i]+idata[i+stride];
}
__syncthreads();
stride = stride*2;
}
スレッド0 スレッド4
41. odata[0]
各Stepでの総和計算(Step 3)
2015/05/28先端GPGPUシミュレーション工学特論41
stepを3に進める
strideを2倍(stride=4)
処理を行うスレッドを選択
スレッド番号をstride*2で割った剰余が0
スレッド番号が8の倍数と0のスレッドが「自
身が読み込む配列要素iの値」と「隣
の配列要素の値」を加算
スレッド間の同期をとる
ストライドを2倍して次のstepに備える
stepがlog2Nに達するまで繰り返す
+ + + +
+ +
+
Step 1
Step 2
Step 3
stride = 1;
for(step=1;step<=STEP;step++){
if(tx%(stride*2)==0){
idata[i]=idata[i]+idata[i+stride];
}
__syncthreads();
stride = stride*2;
}
スレッド0
46. __global__ void reduction1(int *idata,int *odata){
int i = blockIdx.x*blockDim.x + threadIdx.x;//スレッドと配列要素の対応
int tx = threadIdx.x; //スレッド番号
int step; //Reductionの段数をカウントする変数
int stride; //隣の配列要素までの距離
//ストライドを2倍
for(step=1,stride = 1;step<=STEP;step++, stride = stride*2;){
if(tx%(stride*2)==0){
idata[i] = idata[i]+idata[i+stride];
}
__syncthreads();
}
if(tx==0) odata[0] = idata[0];
}
GPUプログラム(Ver.1.1)
2015/05/28先端GPGPUシミュレーション工学特論46
reduction1shift.cu
48. __global__ void reduction1(int *idata,int *odata){
int i = blockIdx.x*blockDim.x + threadIdx.x;//スレッドと配列要素の対応
int tx = threadIdx.x; //スレッド番号
int step; //Reductionの段数をカウントする変数
int stride; //隣の配列要素までの距離
//ストライドを左に一つビットシフト(値が2倍になる)
for(step=1,stride = 1;step<=STEP;step++, stride<<=1;){
if(tx%(stride*2)==0){
idata[i] = idata[i]+idata[i+stride];
}
__syncthreads();
}
if(tx==0) odata[0] = idata[0];
}
GPUプログラム(Ver.1.1)
2015/05/28先端GPGPUシミュレーション工学特論48
reduction1shift.cu
51. __global__ void reduction1(int *idata,int *odata){
int i = blockIdx.x*blockDim.x + threadIdx.x;//スレッドと配列要素の対応
int tx = threadIdx.x; //スレッド番号
int step; //Reductionの段数をカウントする変数 //#define STEPも削除
int stride; //隣の配列要素までの距離
for(stride = 1; stride <= blockDim.x/2; stride<<=1){
if(tx%(2*stride) == 0){
idata[i] = idata[i]+idata[i+stride];
}
__syncthreads();
}
if(tx==0) odata[0] = idata[0];
}
GPUプログラム(Ver.1.2)
2015/05/28先端GPGPUシミュレーション工学特論51
reduction1stride.cu
53. odata[0]
各Stepでの総和計算(Step 2)
2015/05/28先端GPGPUシミュレーション工学特論53
処理を行うスレッドを選択
スレッド番号をstride*2で割った剰余が0
スレッド番号が4の倍数と0のスレッドが「自
身が読み込む配列要素iの値」と「隣
の配列要素の値」を加算
スレッド間の同期をとる
ストライドを2倍し,ストライドがN/2以下
ならループを継続
strideが4になるのでstride=N/2
※NとblockDim.xが等しくなるように設定し
ているため
+ + + +
+ +
+
stride
が2の段
for(stride = 1; stride <= blockDim.x/2;
stride<<=1){
if(tx%(2*stride) == 0){
idata[i]=idata[i]+idata[i+stride];
}
__syncthreads();
}
スレッド0 スレッド4
54. odata[0]
各Stepでの総和計算(Step 3)
2015/05/28先端GPGPUシミュレーション工学特論54
処理を行うスレッドを選択
スレッド番号をstride*2で割った剰余が0
スレッド番号が8の倍数と0のスレッドが「自
身が読み込む配列要素iの値」と「隣
の配列要素の値」を加算
スレッド間の同期をとる
ストライドを2倍し,ストライドがN/2以下
ならループを継続
strideが8になるのでstride>N/2となって
ループを抜ける
+ + + +
+ +
+stride
が4の段
for(stride = 1; stride <= blockDim.x/2;
stride<<=1){
if(tx%(2*stride) == 0){
idata[i]=idata[i]+idata[i+stride];
}
__syncthreads();
}
スレッド0
61. int main(){//Ver.1から変更点無し
int *idata,*odata; //GPU用変数 idata:入力,odata:出力(=総和)
int *host_idata,sum; //CPU用変数 host_idata:初期化用,sum:総和
cudaMalloc( (void **)&idata, Nbytes);
cudaMalloc( (void **)&odata, sizeof(int));
//CPU側でデータを初期化してGPUへコピー
host_idata = (int *)malloc(Nbytes);
init(host_idata);
cudaMemcpy(idata, host_idata,Nbytes, HtoD);
free(host_idata);
reduction2<<< NB, NT >>>(idata, odata);
//GPUから総和の結果を受け取って画面表示
cudaMemcpy(&sum, odata, sizeof(int), DtoH);
printf("sum = %d¥n", sum);
cudaFree(idata);
cudaFree(odata);
return 0;
}
GPUプログラム(Ver.2)
2015/05/28先端GPGPUシミュレーション工学特論61
reduction2.cu
62. __global__ void reduction2(int *idata,int *odata){
int i = blockIdx.x*blockDim.x + threadIdx.x;//スレッドと配列要素の対応
int tx = threadIdx.x; //スレッド番号
int stride; //”隣”の配列要素までの距離
__shared__ volatile int s_idata[N]; //共有メモリの宣言
s_idata[i] = idata[i]; //グローバルメモリから共有メモリへデータをコピー
__syncthreads(); //共有メモリのデータは全スレッドから参照されるので同期を取る
//総和計算はVer.1.2(reduction1stride.cu)と同じ
for(stride = 1; stride <= blockDim.x/2; stride<<=1){
if(tx%(2*stride) == 0){
s_idata[i] = s_idata[i]+s_idata[i+stride];
}
__syncthreads();
}
if(tx==0) odata[0] = s_idata[0];
}
GPUプログラム(Ver.2)
2015/05/28先端GPGPUシミュレーション工学特論62
reduction2.cu
71. int main(){
int *idata,*odata; //GPU用変数 idata:入力,odata:出力(=総和)
int *host_idata,sum; //CPU用変数 host_idata:初期化用,sum:総和
cudaMalloc( (void **)&idata, Nbytes);
cudaMalloc( (void **)&odata, sizeof(int));
//CPU側でデータを初期化してGPUへコピー
//...省略...
sum = 0;//出力用変数odataを0で初期化するためにsumに0を代入して送る
cudaMemcpy(odata, &sum, sizeof(int), cudaMemcpyHostToDevice);
reduction3<<< NB, NT >>>(idata, odata);
//GPUから部分和を受け取って総和を計算
cudaMemcpy(&sum, odata, sizeof(int), cudaMemcpyDeviceToHost);
printf("sum = %d¥n", sum);
cudaFree(idata);
cudaFree(odata);
return 0;
}
GPUプログラム(Ver.3)
2015/05/28先端GPGPUシミュレーション工学特論71
reduction3.cu
72. __global__ void reduction3(int *idata,int *odata){
int i = blockIdx.x*blockDim.x + threadIdx.x;//スレッドと配列要素の対応
int tx = threadIdx.x; //スレッド番号
int stride; //”隣”の配列要素までの距離
__shared__ volatile int s_idata[NT]; //共有メモリの宣言
s_idata[tx] = idata[i]; //グローバルメモリから共有メモリへデータをコピー
__syncthreads(); //共有メモリのデータは全スレッドから参照されるので同期を取る
//総和計算はバージョン1.2(reduction1stride.cu)と同じ
for(stride = 1; stride <= blockDim.x/2; stride<<=1){
if(tx%(2*stride) == 0){
s_idata[tx] = s_idata[tx]+s_idata[tx+stride];
}
__syncthreads();
}
if(tx==0) atomicAdd(odata,s_idata[tx]);
}
GPUプログラム(Ver.3)
2015/05/28先端GPGPUシミュレーション工学特論72
reduction3.cu
75. +
+
各ブロックでの部分和の計算
2015/05/28先端GPGPUシミュレーション工学特論75
処理を行うスレッドを選択
スレッド番号をstride*2で割った剰余が0
スレッド番号が4の倍数と0のスレッドが「自
身が読み込む配列要素iの値」と「隣
の配列要素の値」を加算
スレッド間の同期をとる
ストライドを2倍し,ストライドがN/2以下
ならループを継続
strideが4になるのでstride >
blockDim.x/2となってループを抜ける
+ + + +
+ +strideが
2の段
for(stride = 1; stride <= blockDim.x/;
stride<<=1){
if(tx%(2*stride) == 0){
s_idata[i]=s_idata[i]+s_idata[i+stride];
}
__syncthreads();
}
スレッド0 スレッド0
for(stride = 1; stride <= blockDim.x/2;
stride<<=1){
if(tx%(2*stride) == 0){
s_idata[tx]=s_idata[tx]+s_idata[tx+stride];
}
__syncthreads();
}
79. __device__ double atomicAdd(double *address, const double val){
unsigned long long int *address_as_ull = (unsigned long long int *)address;
unsigned long long int old = *address_as_ull;
unsigned long long int assumed;
do{
assumed = old;
old = atomicCAS(address_as_ull, assumed, __double_as_longlong(val
+ __longlong_as_double(assumed)));
}while(assumed != old);
return __longlong_as_double(old);
}
double型に対するatomicAdd()
2015/07/23先端GPGPUシミュレーション工学特論81
double型を返すデバイス関数を作成
コンパイラが引数の型を判断して適切な関数を選択
関数のオーバーロード
81. __global__ void reduction3(TYPE *idata, TYPE *sum){
int i = blockIdx.x*blockDim.x + threadIdx.x;
int tx = threadIdx.x;
int stride;
__shared__ volatile TYPE s_idata[NT];
s_idata[tx] = idata[i];
__syncthreads();
for(stride = 1; stride <= blockDim.x/2; stride<<=1){
if(tx%(2*stride) == 0){
s_idata[tx] = s_idata[tx]+s_idata[tx+stride];
}
__syncthreads();
}
if(tx==0) atomicAdd(sum,s_idata[tx]);
}
double型に対するatomicAdd()
2015/07/23先端GPGPUシミュレーション工学特論83
reduction3double.cu
83. sum = 0;
cudaMemcpy(odata, &sum,sizeof(TYPE), cudaMemcpyHostToDevice);
reduction3<<< NB, NT >>>(idata, odata);
cudaMemcpy(&sum, odata, sizeof(TYPE), cudaMemcpyDeviceToHost);
printf("array size = %d, sum = %f¥n", N, sum);
cudaFree(idata);
cudaFree(odata);
return 0;
}
double型に対するatomicAdd()
2015/07/23先端GPGPUシミュレーション工学特論85
reduction3double.cu