Upload
-
View
196
Download
1
Embed Size (px)
Citation preview
第3回 GPUプログラム構造の詳細(threadとwarp)
長岡技術科学大学 電気電子情報工学専攻 出川智啓
今回の内容
2015/04/23先端GPGPUシミュレーション工学特論2
2次元的な並列処理
Warp
並列実行の制約
条件分岐
GPUによる1次元的な並列処理
ベクトル和C=A+B 配列要素に対して計算順序の依存性がなく, も単純に並列化可能
・・・
・・・
・・・c[i]
a[i]
b[i]
+ + + + + +
2015/04/23先端GPGPUシミュレーション工学特論3
#include<stdlib.h>#define N (1024*1024)#define Nbytes (N*sizeof(float))
void init(float *a,float *b,float *c){int i;
for(i=0; i<N; i++){a[i] = 1.0;b[i] = 2.0;c[i] = 0.0;
}}
void add(float *a,float *b,float *c){int i;
for(i=0; i<N; i++)c[i] = a[i] + b[i];
}
int main(void){float *a,*b,*c;
a = (float *)malloc(Nbytes);b = (float *)malloc(Nbytes);c = (float *)malloc(Nbytes);
init(a,b,c);add(a,b,c);
free(a);free(b);free(c);return 0;
}
CPUプログラム(メモリの動的確保)
2015/04/23先端GPGPUシミュレーション工学特論4
vectoradd_malloc.c
GPUの構造とカーネルの書き方
プログラムからGPUで実行する関数を呼出
GPUで実行する関数という目印が必要
GPUはPCI‐Exバスを経由してホストと接続
GPUはホストと別に独立したメモリを持つ
関数の実行に必要なデータはGPUのメモリに置く
GPUはマルチスレッド(メニースレッド)で並列処理
関数には1スレッドが実行する処理を書く
関数を実行する際に並列処理の度合いを指定
2015/04/23先端GPGPUシミュレーション工学特論5
GPUの構造とカーネルの書き方
GPUで実行する関数(カーネル)という目印
修飾子__global__を付ける
GPUはPCI‐Exバスを経由してホストと接続
GPUはホストと別に独立したメモリを持つ
カーネルの返値をvoidにする
メモリの動的確保をmallocからcudaMallocに変更
GPUはマルチスレッド(メニースレッド)で並列処理
カーネルには1スレッドが実行する処理を書く
カーネル名と引数の間に<<<1,1>>>を付ける
2015/04/23先端GPGPUシミュレーション工学特論6
GPUで並列に処理を行うには
GPUは低性能の演算器を多数搭載
マルチスレッドで並列処理することで高い性能を達成 マルチスレッドで処理を行わないとCPUよりも遅い
GPUで並列計算を行う時の決まり事カーネルに1スレッドが行う処理を記述
カーネルを実行する際に並列処理の度合いを指定
カーネルの名前の後ろに付けていた<<<1,1>>>は,どのように並列実行するかの指定
2015/04/23先端GPGPUシミュレーション工学特論7
GPUの並列化の階層
グリッド-ブロック-スレッドの3階層
各階層の情報を参照できる変数 x,y,zをメンバにもつdim3型構造体
グリッド(Grid) gridDim グリッド内にあるブロックの数
ブロック(Block) blockIdx ブロックに割り当てられた番号
blockDim ブロック内にあるスレッドの数
スレッド(Thread) threadIdx スレッドに割り当てられた番号
2015/04/23先端GPGPUシミュレーション工学特論8
GPUの並列化の階層
CUDAでは並列化に階層がある 全体の領域(グリッド)をブロックに分割
ブロックの中をスレッドに分割
<<<2, 4>>>
ブロックの数 1ブロックあたりのスレッドの数
ブロックの数×1ブロックあたりのスレッドの数=総スレッド数2 × 4 = 8
2015/04/23先端GPGPUシミュレーション工学特論9
iを一意に決定する
N=8, <<<2, 4>>>で実行
c[i]
a[i]
b[i]
+ + + + + + + +
gridDim.x=2
blockIdx.x=0 blockIdx.x=1
blockDim.x=4blockDim.x=4threadIdx.x=
0 1 2 3 0 1 2 3
threadIdx.x=
i= 0 1 2 3 4 5 6 7= blockIdx.x*blockDim.x + threadIdx.x
2015/04/23先端GPGPUシミュレーション工学特論10
カーネルの書き換え
1スレッドが実行する処理になるよう変更 1スレッドがある添字 i の要素を担当
#define N (1024*1024)#define Nbytes (N*sizeof(float))
__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/04/23先端GPGPUシミュレーション工学特論11
GPUで並列に処理を行うには
GPUは低性能の演算器を多数搭載
マルチスレッドで並列処理することで高い性能を達成
GPUで並列計算を行う時の決まり事
カーネルに1スレッドが行う処理を記述
カーネルを実行する際に並列処理の度合いを指定
ブロック内で複数のスレッドが並列処理
複数のスレッドが異なる添字iにアクセスすることで並列に計算
添字iはスレッド数やブロック数を格納する変数を用いて決定
i = blockIdx.x*blockDim.x + threadIdx.x;
2015/04/23先端GPGPUシミュレーション工学特論12
#define N (1024*1024)#define Nbytes (N*sizeof(float))
__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];
}int main(void){
float *a,*b,*c;
cudaMalloc((void **)&a, Nbytes);cudaMalloc((void **)&b, Nbytes);cudaMalloc((void **)&c, Nbytes);
init<<< N/256, 256>>>(a,b,c);add<<< N/256, 256>>>(a,b,c);
cudaFree(a);cudaFree(b);cudaFree(c);
return 0;}
GPUで並列実行するプログラム
2015/04/23先端GPGPUシミュレーション工学特論13
vectoradd.cu
階層の設定
無制限に設定できるわけではない GPUの世代によって設定できる範囲が変わる
確認にはdeviceQueryやpgaccelinfoを利用
$ pgaccelinfo
2015/04/23先端GPGPUシミュレーション工学特論14
deviceQuery実行結果Device 0: "Tesla C2050"CUDA Driver Version / Runtime Version 5.5 / 5.5CUDA Capability Major/Minor version number: 2.0Total amount of global memory: 2687 MBytes (2817982464 bytes)(14) Multiprocessors, ( 32) CUDA Cores/MP: 448 CUDA CoresGPU Clock rate: 1147 MHz (1.15 GHz)Memory Clock rate: 1500 MhzMemory Bus Width: 384‐bitL2 Cache Size: 786432 bytesMaximum Texture Dimension Size (x,y,z) 1D=(65536), 2D=(65536, 65535), 3D=(2048, 2048, 2048)Maximum Layered 1D Texture Size, (num) layers 1D=(16384), 2048 layersMaximum Layered 2D Texture Size, (num) layers 2D=(16384, 16384), 2048 layersTotal amount of constant memory: 65536 bytesTotal amount of shared memory per block: 49152 bytesTotal number of registers available per block: 32768Warp size: 32Maximum number of threads per multiprocessor: 1536Maximum number of threads per block: 1024Max dimension size of a thread block (x,y,z): (1024, 1024, 64)Max dimension size of a grid size (x,y,z): (65535, 65535, 65535)Maximum memory pitch: 2147483647 bytesTexture alignment: 512 bytesConcurrent copy and kernel execution: Yes with 2 copy engine(s)Run time limit on kernels: NoIntegrated GPU sharing Host Memory: NoSupport host page‐locked memory mapping: YesAlignment requirement for Surfaces: YesDevice has ECC support: EnabledDevice supports Unified Addressing (UVA): YesDevice PCI Bus ID / PCI location ID: 2 / 0
2015/04/23先端GPGPUシミュレーション工学特論15
pgaccelinfoの実行結果Device Number: 0Device Name: Tesla M2050Device Revision Number: 2.0Global Memory Size: 2817982464Number of Multiprocessors: 14Number of Cores: 448Concurrent Copy and Execution: YesTotal Constant Memory: 65536Total Shared Memory per Block: 49152Registers per Block: 32768Warp Size: 32Maximum Threads per Block: 1024Maximum Block Dimensions: 1024, 1024, 64Maximum Grid Dimensions: 65535 x 65535 x 65535Maximum Memory Pitch: 2147483647BTexture Alignment: 512BClock Rate: 1147 MHzInitialization time: 4222411 microsecondsCurrent free memory: 2746736640Upload time (4MB): 2175 microseconds ( 829 ms pinned)Download time: 2062 microseconds ( 774 ms pinned)Upload bandwidth: 1928 MB/sec (5059 MB/sec pinned)Download bandwidth: 2034 MB/sec (5418 MB/sec pinned)
2015/04/23先端GPGPUシミュレーション工学特論16
pgaccelinfo実行結果 Revision Number: 2.0
Global Memory Size: 2817982464
Warp Size: 32
Maximum Threads per Block: 1024
Maximum Block Dimensions: 1024, 1024, 64
Maximum Grid Dimensions: 65535 x 65535 x 65535
どのような機能を有しているか
実行時のパラメータ
選択の際に重要
各方向の 大値
1ブロックあたりのスレッド数は 大1024 (1024, 1, 1), (1, 1024, 1)
(32, 32, 1), (4, 4, 64)など
2015/04/23先端GPGPUシミュレーション工学特論17
GPUの階層の情報(dim3型構造体)
2015/04/23先端GPGPUシミュレーション工学特論18
gridDim(グリッド内にあるブロックの数) gridDim.x, gridDim.y, gridDim.z
blockIdx(ブロックに割り当てられた番号) blockIdx.x, blockIdx.y, blockIdx.z
blockDim(ブロック内にあるスレッドの数) blockDim.x, blockDim.y, blockDim.z
threadIdx(スレッドに割り当てられた番号) threadIdx.x, threadIdx.y, threadIdx.z
2次元的な並列処理
2015/04/23先端GPGPUシミュレーション工学特論19
例えば画像処理のフィルタなど あるピクセルの周囲情報を利用して効果を計算
画像を矩形領域で分割した2次元的な並列処理が自然
元画像 輪郭抽出後
2次元的な並列処理
2015/04/23先端GPGPUシミュレーション工学特論20
ブロックとスレッドを2次元的に設定 1スレッドが1ピクセル(配列の1要素)を処理
スレッドを2次元的に配置してブロックを構成
想定されるカーネル
1スレッドが2次元配列の1要素を計算 1スレッドがある添字 i,j の要素を担当
#define Nx (1920)#define Ny (1080)
__global__ void filter(...){
int i = ; //1スレッドが担当する配列要素の添字を決定int j = ; //
picture[i][j] = ...;
}
2015/04/23先端GPGPUシミュレーション工学特論21
i, jを一意に決定する
2015/04/23先端GPGPUシミュレーション工学特論22
Nx=8, Ny=8, x,y方向スレッド数4,ブロック数2
blockIdx.x=0 blockIdx.x=1
blockIdx.y=0
blockIdx.y=1
gridDim.x=2
gridDim.y=2
blockDim.x=4
blockDim.y=4
threadIdx.x=threadIdx.y=
i, jを一意に決定する
2015/04/23先端GPGPUシミュレーション工学特論23
Nx=8, Ny=8, x,y方向スレッド数4,ブロック数2 gridDim.x=2, gridDim.y=2 blockDim.x=4,blockDim.y=4
(0,0)(1,0)(2,0)(3,0)(0,0)
(3,3) (3,3)
(0,1)(1,1)(2,1)(3,1)
(0,2)(1,2)(2,2)(3,2)
(0,3)(1,3)(2,3)(3,3) (3,3)
(0,0) (0,0)
block(0,0) block(1,0)
block(0,1) block(1,1)
thread
threadIdx.xthreadIdx.y
i= 0 1 2 3 4 5 6 7j=
01
23
45
67
i, jを一意に決定する
2015/04/23先端GPGPUシミュレーション工学特論24
Nx=8, Ny=8, x,y方向スレッド数4,ブロック数2 i = blockIdx.x*blockDim.x + threadIdx.x j = blockIdx.y*blockDim.y + threadIdx.y
(0,0)(1,0)(2,0)(3,0)(0,0)
(3,3) (3,3)
(0,1)(1,1)(2,1)(3,1)
(0,2)(1,2)(2,2)(3,2)
(0,3)(1,3)(2,3)(3,3) (3,3)
(0,0) (0,0)
block(0,0) block(1,0)
block(0,1) block(1,1)
thread
threadIdx.xthreadIdx.y
i= 0 1 2 3 4 5 6 7j=
01
23
45
67
1次元による配列で2次元配列の表現
2015/04/23先端GPGPUシミュレーション工学特論25
mallocやcudaMallocは1次元配列を宣言 2次元配列の宣言は不可能
1次元配列を宣言し,2次元配列的に参照
2次元配列の場合
1次元配列の場合
picutre[1][2] = 11
picture[_______] = 11
Nx=8
1*8 + 2
picture[i][j] picture[i*Ny+j]
j
i12345
9101112
678
picture[Nx][Ny]
Ny=8
想定されるカーネル
1スレッドが2次元配列の1要素を計算 1スレッドがある添字 i,j の要素を担当
#define Nx (1920)#define Ny (1080)
__global__ void filter(...){
int i = blockIdx.x*blockDim.x + threadIdx.x;int j = blockIdx.y*blockDim.y + threadIdx.y;
picture[i*Ny + j] = ...;
}
2015/04/23先端GPGPUシミュレーション工学特論26
picture[j*Nx + i] = ... の方が適切第4回GPUのメモリ階層の詳細(共有メモリ)を参照の事
2次元的な並列度の指定
2015/04/23先端GPGPUシミュレーション工学特論27
<<<,>>>の中にどのように数字を書くか 1次元の場合は数字を書くことができた
2次元,3次元は数字を並べて書くことができない
dim3型変数を利用
int main(void){
dim3 block(2,2,1), thread(4,4,1);filter<<<block, thread>>>(...);
filter<<<dim3(2,2,1), dim3(4,4,1)>>>(...);
}
dim3型変数block, threadを利用
・・・
あるいは直接dim3型として記述・・・
#define Nx (1024)#define Ny (1024)#define Nbytes (Nx*Ny*sizeof(float))#define NTx (16)#define NTy (16)#define NBx (Nx/NTx)#define NBy (Ny/NTy)
__global__ void init(float *a, float *b,float *c){
int i = blockIdx.x*blockDim.x + threadIdx.x;int j = blockIdx.y*blockDim.y + threadIdx.y;int ij = i*Ny + j;
a[ij] = 1.0;b[ij] = 2.0;c[ij] = 0.0;
}__global__ void add(float *a, float *b,
float *c){int i = blockIdx.x*blockDim.x + threadIdx.x;int j = blockIdx.y*blockDim.y + threadIdx.y;int ij = i*Ny + j;
c[ij] = a[ij] + b[ij];}
int main(void){float *a,*b,*c;dim3 thread(NTx, NTy, 1);dim3 block(NBx, NBy, 1);
cudaMalloc( (void **)&a, Nbytes);cudaMalloc( (void **)&b, Nbytes);cudaMalloc( (void **)&c, Nbytes);
init<<< block, thread >>>(a,b,c);add<<< block, thread >>>(a,b,c);
cudaFree(a);cudaFree(b);cudaFree(c);return 0;
}
ベクトル和(2次元並列版)
2015/04/23先端GPGPUシミュレーション工学特論28
vectoradd2d.cu
int ij=j*Nx+iの方が適切
int ij=j*Nx+iの方が適切
一般的な形
グリッドは必ず1個 ブロックは3次元的に配置可能
スレッドは3次元的に配置可能
各方向の 大値はGPUの世代によって変化
Grid
Block(i,j,k)
Thread(i,j,k)
2015/04/23先端GPGPUシミュレーション工学特論29
GPUの並列処理
GPUはどのようにスレッドを管理しているか?
Streaming Multiprocessor内のCUDA Core数 Tesla世代 8 Fermi世代 32 Kepler世代 192 Maxwell世代 128
Warp(ウォープ)という処理の単位の導入 32スレッドをまとめて1 Warp 1 Warpごとに処理を実行
2015/04/23先端GPGPUシミュレーション工学特論30
256スレッドの同時実行は不可能
Warpによる並列実行
処理はWarp単位で実行 32スレッドごとに処理を実行
1ブロックあたりのスレッド数が256=Warp 8個
ある命令を発行するとWarp単位で同じ命令を実行 Warp内でCUDA CoreはSIMD的に動作
複数のまとまったスレッドが同じ演算を同時に実行
SIMT(Single Instruction Multiple Threads)
SIMD(Single Instruction Multiple Data streams) 複数のまとまったデータに対して同じ演算を同時に実行
命令は一つ,その命令が同時に多くのデータに対して適用されるアーキテクチャ
2015/04/23先端GPGPUシミュレーション工学特論31
Warpによる並列実行の利点
パイプライン実行 あるWarpがグローバルメモリへアクセス,データ待ち
その他の実行可能なWarpが計算を実行
グローバルメモリへのアクセスのレイテンシを隠蔽
2015/04/23先端GPGPUシミュレーション工学特論36
計算 メモリアクセス 計算Warp0
Warp1
Warp2
Warp3
実行開始 処理時間
計算 メモリアクセス 計算
計算 メモリアクセス 計算
計算 メモリアクセス
・・・
・・・
Warpによる並列実行の利点
レイテンシ 命令の発行から結果が得られるまでの(遅延)時間
簡単な命令でも数サイクル(クロック)必要
グローバルメモリへのアクセスは数百サイクル必要
CPUはプリフェッチやOut of Order実行でレイテンシを隠蔽
GPUでは各コアに対してCPUのような機能を実装できない
コアの数,面積,電力の問題
2015/04/23先端GPGPUシミュレーション工学特論37
Warpによる並列実行の利点
複数のWarpを切り替えながら処理を実行することでメモリアクセスのレイテンシを隠蔽
1ブロックあたりのスレッド数が多いと,Warpの実行に必要な資源が不足
38 2015/04/23先端GPGPUシミュレーション工学特論
計算 メモリアクセス 計算Warp0
Warp1
Warp2
Warp3
実行開始 処理時間
計算 メモリアクセス 計算
計算 メモリアクセス 計算
計算 メモリアクセス
・・・
・・・
Warpの同時実行
2015/04/23先端GPGPUシミュレーション工学特論39
一つのブロック内で使用している資源(レジスタ,共有メモリ)によって同時実行できるWarpの数が変化 同時実行可能なWarpが多いほどレイテンシ隠蔽が容易
同時に実行されるWarpの数 Active Warp Active Warpを多くすることが高速化に繋がる
占有率(Occupancy) SM内で並列実行されているスレッド数 と SMあたりの 大で実行できるスレッド数 の比
1に近いほどよいが,占有率が高い=高速 ではない
並列実行に対する制約
2015/04/23先端GPGPUシミュレーション工学特論40
GPUの構成(資源の量,スケジューラ)に起因するハードウェア的・ソフトウェア的制約
Tesla世代 Fermi世代
Warpサイズ 32スレッド
1ブロックあたりのスレッド数 512 1024
1SMあたりの 大スレッド数 1024 1536
1SMあたりのWarp数 32 48
1SMあたりのブロック数 8 8
1SMあたりの共有メモリサイズ 16384 byte 49152 byte
1SMあたりの32bitレジスタ数 16384本 32768本
https://www.softek.co.jp/SPG/Pgi/TIPS/public/accel/gpu‐accel2.html
並列実行に対する制約
2015/04/23先端GPGPUシミュレーション工学特論41
1ブロックあたりのスレッド数を256に設定 1ブロックあたりのWarp数
256thread/block / 32thread/Warp=8 Warp/block
1SMが処理するブロック数(1SMあたり 大48Warpの処理が可能)
48 Warp/SM / 8 Warp/block = 6 block/SM(<8) 1SMあたり6ブロックを並列処理すればよい( 大8ブロック)
同時実行されるWarp数は
8 Warp/block×6 block/SM = 48 Warp/SM 1SMあたり 大48 Warp同時実行できるので,占有率は
48 Warp/SM /48 Warp/SM = 1.00 (=100%)
太字:利用するGPUによって決定下線:ユーザの設定によって決定斜体:処理内容やコンパイラによって決定
並列実行に対する制約
2015/04/23先端GPGPUシミュレーション工学特論42
1ブロックあたりのスレッド数を 64に設定 1ブロックあたりのWarp数
64thread/block / 32thread/Warp=2 Warp/block
1SMが処理するブロック数(1SMあたり 大48Warpの処理が可能)
48 Warp/SM / 2 Warp/block = 24 block/SM(>8) 1SMあたり8ブロックを並列処理可能(24ブロックの処理は不可能)
同時実行されるWarp数は
2 Warp/block×8 block/SM = 16 Warp/SM 1SMあたり 大48 Warp同時実行できるので,占有率は
16 Warp/SM /48 Warp/SM = 0.33 (=33%)
太字:利用するGPUによって決定下線:ユーザの設定によって決定斜体:処理内容やコンパイラによって決定
並列実行に対する制約
2015/04/23先端GPGPUシミュレーション工学特論43
並列実行されるブロックの数 1ブロックあたりの共有メモリやレジスタの使用量で制限
1ブロックあたりのスレッド数がNt 1ブロックが共有メモリをS[byte]利用していると
49152/S [block/SM] 1スレッドがレジスタをR[本]利用していると
32768/(Nt×R) [block/SM]
並列実行されるブロック数
min{8, 48/(Nt/32), 49152/S, 32768/(Nt×R)}
太字:利用するGPUによって決定下線:ユーザの設定によって決定斜体:処理内容やコンパイラによって決定
Warp内のスレッドの実行
2015/04/23先端GPGPUシミュレーション工学特論44
Warp内では32スレッドが同じ命令を実行 Warp内でCUDA CoreはSIMD的に動作(SIMT型)
プログラム的には異なる動作をさせることも可能 ブロック内のスレッド番号 threadIdx を基にif文を記述しても正しく実行(=厳密なSIMDではない)
__global__ void kernel(){if(threadIdx.x == 0){
処理1}else{
処理2}
}int main(void){
kernel<<<1,32>>>();}
1 Warpだけでカーネルを実行・・・
0番目のスレッドだけ処理1を実行・・・
残りの31スレッドは処理2を実行・・・
ブロック内での条件分岐
2015/04/23先端GPGPUシミュレーション工学特論45
Warp内では32スレッドが同じ命令を実行 Warp内でCUDA CoreはSIMD的に動作(SIMT型)
条件分岐もWarp単位で判定
Warp内の全スレッドの分岐先が同じ Warp内の全スレッドが,分岐先のみの処理を実行
Warp内でスレッドの分岐先が異なる Warp内の全スレッドが,全ての分岐先の処理を実行
各スレッドで真になる条件の処理のみを採用
branch divergence, divergent branch, diverged branchなどと呼ばれる
__global__ void no_diverge(){if(threadIdx.x < 32){
処理1}else{
処理2}
}
__global__ void diverge(){if(threadIdx.x % 8 == 0){
if(threadIdx.x & 1 == 1) a+=1;else a+=2;
}else{if(threadIdx.x & 1 == 1) a+=2;else a+=1;
}}
int main(void){no_diverge<<<1,64>>>();
diverge<<<1,64>>>();}
ブロック内での条件分岐
2015/04/23先端GPGPUシミュレーション工学特論46
2 Warpでカーネルを実行・・・
0~31スレッド(=Warp 0)は処理1を実行・・・
32~63スレッド(=Warp 1)は処理2を実行・・・divergeしない
__global__ void no_diverge(){if(threadIdx.x < 32){
処理1}else{
処理2}
}
__global__ void diverge(){if(threadIdx.x % 8 == 0){
if(threadIdx.x & 1 == 1) a+=1;else a+=2;
}else{if(threadIdx.x & 1 == 1) a+=2;else a+=1;
}}
int main(void){no_diverge<<<1,64>>>();
diverge<<<1,64>>>();}
ブロック内での条件分岐
2015/04/23先端GPGPUシミュレーション工学特論47
diverge
divergeしない
スレッド番号が8の倍数(0も含む)・・・
スレッド番号が8の倍数以外・・・
2 Warpでカーネルを実行・・・
0~31スレッド(=Warp 0)は処理1を実行・・・
32~63スレッド(=Warp 1)は処理2を実行・・・
__global__ void no_diverge(){if(threadIdx.x < 32){
処理1}else{
処理2}
}
__global__ void diverge(){if(threadIdx.x % 8 == 0){
if(threadIdx.x & 1 == 1) a+=1;else a+=2;
}else{if(threadIdx.x & 1 == 1) a+=2;else a+=1;
}}
int main(void){no_diverge<<<1,64>>>();
diverge<<<1,64>>>();}
ブロック内での条件分岐
2015/04/23先端GPGPUシミュレーション工学特論48
divergeしない
スレッド番号が奇数なら1を加算・・・スレッド番号が偶数なら2を加算・・・
スレッド番号が奇数なら2を加算・・・
スレッド番号が偶数なら1を加算・・・
diverge
diverge
スレッド番号が8の倍数(0も含む)・・・
スレッド番号が8の倍数以外・・・
2 Warpでカーネルを実行・・・
0~31スレッド(=Warp 0)は処理1を実行・・・
32~63スレッド(=Warp 1)は処理2を実行・・・
__global__ void no_diverge(){if(threadIdx.x < 32){
処理1}else{
処理2}
}
__global__ void diverge(){if(threadIdx.x % 8 == 0){
if(threadIdx.x & 1 == 1) a+=1;else a+=2;
}else{if(threadIdx.x & 1 == 1) a+=2;else a+=1;
}}
int main(void){no_diverge<<<1,64>>>();
diverge<<<1,64>>>();}
ブロック内での条件分岐
2015/04/23先端GPGPUシミュレーション工学特論49
スレッド番号が奇数なら1を加算・・・スレッド番号が偶数なら2を加算・・・
スレッド番号が奇数なら2を加算・・・
スレッド番号が偶数なら1を加算・・・
diverge
diverge
スレッド番号が8の倍数(0も含む)・・・
スレッド番号が8の倍数以外・・・
全てのifがdivergeするので,合計4通りの計算が行われる
divergeしない
2 Warpでカーネルを実行・・・
0~31スレッド(=Warp 0)は処理1を実行・・・
32~63スレッド(=Warp 1)は処理2を実行・・・
ブロック間での分岐
2015/04/23先端GPGPUシミュレーション工学特論50
ブロック内の全スレッドが分岐先の処理を実行
divergent branchのような現象は発生しない 条件判断に要する時間だけで処理の切替が可能
GPUはifの処理が苦手なので,ifの使用は極力控える
__global__ void kernel(){if(blockIdx.x == 0){
処理1}else{
処理2}
}int main(void){
kernel<<<2,32>>>();}
ブロック数2でカーネルを実行・・・
0番目のブロックに属する全スレッドが処理1を実行・・・
それ以外のブロックに属する全スレッドが処理2を実行・・・