第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.5
CUDA Capability Major/Minor version number:    2.0
Total amount of global memory:                 2687 MBytes (2817982464 bytes)
(14) Multiprocessors, ( 32) CUDA Cores/MP:     448 CUDA Cores
GPU Clock rate:                                1147 MHz (1.15 GHz)
Memory Clock rate:                             1500 Mhz
Memory Bus Width:                              384‐bit
L2 Cache Size:                                 786432 bytes
Maximum 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 layers
Maximum Layered 2D Texture Size, (num) layers  2D=(16384, 16384), 2048 layers
Total amount of constant memory:               65536 bytes
Total amount of shared memory per block:       49152 bytes
Total number of registers available per block: 32768
Warp size:                                     32
Maximum number of threads per multiprocessor:  1536
Maximum number of threads per block:           1024
Max 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 bytes
Texture alignment:                             512 bytes
Concurrent copy and kernel execution:          Yes with 2 copy engine(s)
Run time limit on kernels:                     No
Integrated GPU sharing Host Memory:            No
Support host page‐locked memory mapping:       Yes
Alignment requirement for Surfaces:            Yes
Device has ECC support:                        Enabled
Device supports Unified Addressing (UVA):      Yes
Device PCI Bus ID / PCI location ID:           2 / 0
2015/04/23先端GPGPUシミュレーション工学特論15
pgaccelinfoの実行結果
Device Number:                 0
Device Name:                   Tesla M2050
Device Revision Number:        2.0
Global Memory Size:            2817982464
Number of Multiprocessors:     14
Number of Cores:               448
Concurrent Copy and Execution: Yes
Total Constant Memory:         65536
Total Shared Memory per Block: 49152
Registers per Block:           32768
Warp Size:                     32
Maximum Threads per Block:     1024
Maximum Block Dimensions:      1024, 1024, 64
Maximum Grid Dimensions:       65535 x 65535 x 65535
Maximum Memory Pitch:          2147483647B
Texture Alignment:             512B
Clock Rate:                    1147 MHz
Initialization time:           4222411 microseconds
Current free memory:           2746736640
Upload 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=0blockIdx.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.x
threadIdx.y
i= 0 1 2 3 4 5 6 7
j=01234567
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.x
threadIdx.y
i= 0 1 2 3 4 5 6 7
j=01234567
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
i
1
2
3
4
5
9
10
11
12
6
7
8
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を実行・・・

2015年度先端GPGPUシミュレーション工学特論 第3回 GPUプログラム構造の詳細 (threadとwarp)