Upload
-
View
208
Download
2
Embed Size (px)
Citation preview
第4回 GPUのメモリ階層の詳細(共有メモリ)
長岡技術科学大学 電気電子情報工学専攻 出川智啓
今回の内容
2015/05/07先端GPGPUシミュレーション工学特論2
GPUのメモリ
グローバルメモリ
共有メモリ バンクコンフリクト
共有メモリを利用した配列の転置
GPU(Graphics Processing Unit)とは
画像処理専用のハードウェア 具体的には画像処理用のチップ
チップ単体では販売されていない
PCI‐Exカードで販売 チップ単体と区別せずにGPUと呼ぶことも多い
ノートPCに搭載
PCI‐Exカードとして販売されるGPUには,ビデオメモリと呼ばれるRAMが搭載
2015/05/07先端GPGPUシミュレーション工学特論3
GPUの主要部品
2015/05/07先端GPGPUシミュレーション工学特論4
基盤
画面出力端子
電源入力端子
GPU(チップ)+冷却部品
メモリ
このメモリの特性の把握が重要
http://www.geforce.com/whats‐new/articles /introducing‐the‐geforce‐gtx‐780に公開されている写真を基に作成
画面出力端子
PCI‐Ex端子
電源入力端子
メモリ
チップ
メモリの階層
2015/05/07先端GPGPUシミュレーション工学特論5
オフチップメモリ GPUのチップ外部に置かれたメモリ
基板上に実装
ビデオメモリ(数GB)
オンチップメモリ GPUのチップ内部に置かれたメモリ
レジスタ
レベル1(L1)キャッシュ
レベル2(L2)キャッシュ
高速,容量小
低速,容量大
CPUの構造に類似
メモリの階層
2015/05/07先端GPGPUシミュレーション工学特論6
CPUのメモリ階層に類似
CPUのメモリ階層 オフチップ メインメモリ
オンチップ レジスタ,L1,L2(,L3)キャッシュ
GPUメモリの独自の特徴 GPUのチップから読み書き可能か
CPUから読み書き可能か
メモリの階層
2015/05/07先端GPGPUシミュレーション工学特論7
CPUのメモリ階層 コアごとにL2キャッシュ,全体でL3キャッシュを持つこともある
メインメモリ
L2キャッシュ
L1キャッシュ L1キャッシュ L1キャッシュ
コア
レジスタ レジスタ
コア
レジスタ レジスタ
・・・
・・・
チップ
コア
レジスタ レジスタ
メモリの階層
2015/05/07先端GPGPUシミュレーション工学特論8
GPUのメモリ階層 CPUにはない独自のメモリを複数持つ
グローバルメモリ
L2キャッシュ
L1キャッシュ 共有メモリ
コア
レジスタ
チップ
テクスチャメモリ
コンスタントメモリ
ローカルメモリ
テクスチャキャッシュ
コンスタントキャッシュ
メモリの種類
オンチップメモリ(GPUのチップ内部のメモリ) 高速アクセス,小容量
CPUからはアクセス不可
L1キャッシュと共有メモリは一定サイズを共用
2015/05/07先端GPGPUシミュレーション工学特論9
L1キャッシュ/共有(シェアード)メモリ
レジスタ
容量 小 小
速度 高速 高速
GPUからの読み書き
読み書き可ブロック内の全スレッドが同じメモリにアクセス(データを共有する)ことが可
能*
読み書き可各スレッドが異なるレジスタ
にアクセス
CPUからのアクセス
読み書き不可 読み書き不可
*同じメモリ,異
なるメモリについては後ろのスライドで説明
メモリの種類
オフチップメモリ(GPUのチップ外部のメモリ) 低速アクセス,大容量
CPUから直接アクセス可能
ローカルメモリだけはアクセス不可
2015/05/07先端GPGPUシミュレーション工学特論10
グローバルメモリ ローカルメモリ テクスチャメモリ コンスタントメモリ
容量 大 小 大 小
速度 低速 低速 高速*1 高速*1
GPUからの読み書き
読み書き可全てのスレッドが同じメモリにアクセス可能*2
読み書き可各スレッドが異なるメモリにアクセス*2
読み込み可全てのスレッドが同じメモリにアクセス可能*2
読み込み可全てのスレッドが同じメモリにアクセス*2
CPUからのアクセス
読み書き可 読み書き不可 読み書き可 読み書き可
*1キャッシュが効く場合*2同じメモリ,異なるメモリについては後ろのスライドで説明
同じメモリ
2015/05/07先端GPGPUシミュレーション工学特論11
複数のスレッドがメモリアドレスを共有 複数のスレッドが変数を共有
他のスレッドが書き込んだデータを読むことが可能
共有できるスレッドの範囲はメモリの種類によって変化
//a,b[]がグローバルメモリに確保されている場合*1
__global__ void kernel(...){int i = blockIdx.x*blockDim.x + threadIdx.x;:
b[i] = i; //スレッドiが配列要素b[i]に自身のスレッド番号を代入//b[0,1,...,i‐1,i,...]の値は0,1,...,i‐1,i,...
a = b[i‐1];*2 //スレッドi‐1が書き込んだ値を読み*3,aに代入: // 後に書き込まれたaの値を全スレッドが共有
} *1 あくまで動作のイメージを説明するための例で正しく実行できない*2 iが0の場合は考えない*3 配列bを全スレッドが共有しているので,書き込んだ値以外を読む事が可能
異なるメモリ
2015/05/07先端GPGPUシミュレーション工学特論12
メモリアドレスが共有できず,一つのスレッドのみがそのメモリアドレスにアクセス あるスレッドが宣言した変数へアクセスできない
//a,b[]がレジスタに確保されている場合*1
__global__ void kernel(...){int i = blockIdx.x*blockDim.x + threadIdx.x;:
b[i] = i; //スレッドiが配列要素b[i]に自身のスレッド番号を代入//b[0,1,...,i‐1,i,...]の値はb[i]以外不定
a = b[i‐1];*2 //b[i‐1]の値(不定)をaに代入*3
: //aの値はスレッドによって異なる} *1 あくまで動作のイメージを説明するための例で正しく実行できない
*2 iが0の場合は考えない*3 配列bは他のスレッドからアクセスできないため,代入した値以外は不定のまま
共有メモリとL1キャッシュは一定サイズを共用
グローバルメモリへのアクセスはL2キャッシュ経由*1
Fermi世代では標準でL1キャッシュも有効化*2
メモリの種類
2015/05/07先端GPGPUシミュレーション工学特論13
オフチップメモリ
オンチップメモリ
L2キャッシュ
コンスタントメモリ
テクスチャメモリ
GPUChip
レジスタ
レジスタ
レジスタ
レジスタ
CUDA Core
CUDA Core
CUDA Core
CUDA Core
L1キャッシュ
共有メモリ
SM
レジスタ
レジスタ
レジスタ
レジスタ
CUDA Core
CUDA Core
CUDA Core
CUDA Core
L1キャッシュ
共有メモリ
SM
グローバルメモリ
ローカルメモリ
ローカルメモリ
ローカルメモリ
ローカルメモリ
・・・
・・・*1Tesla世代でもテクスチャキャッシュとコンスタントキャッシュは存在*2Kepler世代では標準でL1キャッシュが無効化
ホストメモリ
メモリの種類と並列化階層の対応
オンチップメモリ
ブロックまたはスレッドごとに異なる値を持つ
オフチップメモリ GPU全体で共通の値を持つ
ローカルメモリはレジスタが不足した時に使われる
2015/05/07先端GPGPUシミュレーション工学特論14
各GPU(Grid)内でデータを共有
各ブロック内でデータを共有
各スレッドが個別のデータを保有
L2キャッシュ
コンスタントメモリ
テクスチャメモリ
Grid
レジスタ
レジスタ
レジスタ
レジスタ
Thread 0
Thread 1
Thread 2
Thread 3
L1キャッシュ
共有メモリ
Block(0,0,0)
レジスタ
レジスタ
レジスタ
レジスタ
Thread 0
Thread 1
Thread 2
Thread 3
L1キャッシュ
共有メモリ
Block(1,0,0)
グローバルメモリ
ローカルメモリ
ローカルメモリ
ローカルメモリ
ローカルメモリ
・・・
・・・
ホストメモリ
L2キャッシュ
コンスタントメモリ
テクスチャメモリ
GPU
レジスタ
レジスタ
レジスタ
レジスタ
CUDA Core
CUDA Core
CUDA Core
CUDA Core
L1キャッシュ
共有メモリ
SM
レジスタ
レジスタ
レジスタ
レジスタ
CUDA Core
CUDA Core
CUDA Core
CUDA Core
L1キャッシュ
共有メモリ
SM
グローバルメモリ
ローカルメモリ
ローカルメモリ
ローカルメモリ
ローカルメモリ
・・・
・・・
グローバルメモリ
ビデオメモリ(数GB) CPUのメインメモリに相当
読み込みがある一定サイズでまとめて行われる
レイテンシが大きい
効率よくアクセスするための条件がある コアレスアクセス
アラインアクセス
2015/05/07先端GPGPUシミュレーション工学特論15
Tesla世代ではコアレスアクセスの条件にメモリのアラインが含まれている
Chip
ホストメモリ
グローバルメモリへのアクセス(Fermi世代以降)
2015/05/07先端GPGPUシミュレーション工学特論16
メモリ操作の命令はWarpごとに発行 1 Warp内の32スレッドが協調
各スレッドのメモリアクセス要求を一つに集約
メモリアクセス要求の処理 128バイトもしくは32バイト単位
すべてL2キャッシュを通過
アーキテクチャによってはL1キャッシュも通過
L1キャッシュの使用はコンパイルオプションで設定可能
Fermi世代は標準で使用,Kepler世代は標準で未使用
グローバルメモリへのアクセス(Fermi世代以降)
2015/05/07先端GPGPUシミュレーション工学特論17
コアレスメモリアクセス(coalesce access) 1 Warp内の32スレッドが隣り合ったメモリにアクセス
データサイズが4バイトの時,スレッド1がアクセスするメモリアドレスはスレッド0がアクセスするメモリアドレス+4
メモリアドレスの連続性に着目
アラインメモリアクセス(align access) Warpがアクセスするデータの先頭アドレスがキャッシュ粒度の倍数
L2キャッシュのみを使う場合は32バイトの倍数
L1キャッシュとL2キャッシュを使う場合は128バイトの倍数
キャッシュされる読込
2015/05/07先端GPGPUシミュレーション工学特論18
L1キャッシュとL2キャッシュを通過
読込は128バイト単位で実行 L1キャッシュのキャッシュラインのサイズで実行
読込の評価に利用する用語 メモリトランザクション
メモリのアクセス要求に対して排他的にメモリにアクセスして行う処理の単位
バスの利用率
必要なデータサイズ/読込完了に必要な読込データサイズ
アライン/コアレスアクセス
Warp内の全スレッドが要求するメモリアドレスが128バイトの範囲内
全スレッドが隣り合ったメモリにアクセス
先頭データのメモリアドレスが128バイトの倍数
読込に必要な128バイトのトランザクションは一つ
読み込んだデータ全てを利用
バスの利用率は128/128=100%
0 16
32
48
64
80
96
112
キャッシュされる読込の例
2015/05/07先端GPGPUシミュレーション工学特論19
128
144
160
176
192
208
224
240
012345678910111213141516171819202122232425262728293031
256
272
288
304
320
336
352
358
Warp内でのスレッドID(カーネル内でのスレッド番号とは異なる)
メモリアドレス
メモリアドレスに対するスレッドのアクセス要求
読込に必要な128バイトトランザクション
128バイトのキャッシュライン読み込むデータ
キャッシュされる読込の例
2015/05/07先端GPGPUシミュレーション工学特論20
アライン/アンコアレスアクセス
Warp内の全スレッドが要求するメモリアドレスが128バイトの範囲内(一つのキャッシュラインに収まる)
各スレッドがアクセスするメモリアドレスが不連続
先頭データのメモリアドレスが128バイトの倍数
読込に必要な128バイトのトランザクションは一つ
読み込んだデータ全てを利用
バスの利用率は128/128=100%
0 16
32
48
64
80
96
112
012345678910111213141516171819202122232425262728293031
128
144
160
176
192
208
224
240
256
272
288
304
320
336
352
358
キャッシュされる読込の例
2015/05/07先端GPGPUシミュレーション工学特論21
ミスアライン/コアレスアクセス
Warpがアクセスするデータの先頭アドレスが128の倍数ではない(一つのキャッシュラインに収まらない)
全スレッドが隣り合ったメモリにアクセス
先頭データのメモリアドレスが128バイトの倍数ではない
読込に必要な128バイトのトランザクションは二つ
読み込んだデータの半分だけを利用
バスの利用率は128/(128×2)=50%
012345678910111213141516171819202122232425262728293031
0 16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
256
272
288
304
320
336
352
358
キャッシュされる読込の例
2015/05/07先端GPGPUシミュレーション工学特論22
ミスアライン/コアレスアクセス
Warp内の全スレッドが同じアドレスにアクセス要求
一つのキャッシュラインに収まる
読込に必要な128バイトのトランザクションは一つ
読み込んだ128バイトのうち利用されるのは4バイトのみ
バスの利用率は4/128=3.125%
012345678910111213141516171819202122232425262728293031
0 16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
256
272
288
304
320
336
352
358
キャッシュされる読込の例
2015/05/07先端GPGPUシミュレーション工学特論23
ミスアライン/コアレスアクセス
Warp内の各スレッドが広範囲に点在するデータにアクセス
複数のキャッシュラインにまたがる(上の例では3個)
読込に必要な128バイトのトランザクションは 悪で32個 読み込んだデータのうち128バイトのみを利用
バスの利用率は 悪で128/(128×32)=3.125%上の例では128/(128×3)=33.3%
何度も読み込むための待ちが発生
012345678910111213141516171819202122232425262728293031
0 16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
256
272
288
304
320
336
352
358
キャッシュされない読込
2015/05/07先端GPGPUシミュレーション工学特論24
L1キャッシュを無効化
L2キャッシュのみを通過
読込は32バイト単位で実行 メモリセグメントのサイズで実行
細かい単位で実行されるため,ミスアライン・アンコアレスメモリアクセスのパフォーマンスが改善される可能性がある
セグメント
メモリの管理方式の一つ
まとまった大きさで管理されたメモリ空間
アライン/コアレスアクセス
Warp内の全スレッドが要求するメモリアドレスが128バイト(四つのセグメントに収まる)
全スレッドが隣り合ったメモリにアクセス
先頭データのメモリアドレスが32バイトの倍数
読込に必要な32バイトのトランザクションは四つ
読み込んだデータ全てを利用
バスの利用率は128/(32×4)=100%
キャッシュされない読込の例
2015/05/07先端GPGPUシミュレーション工学特論25
012345678910111213141516171819202122232425262728293031
0 16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
256
272
288
304
320
336
352
358
Warp内でのスレッドID(カーネル内でのスレッド番号とは異なる)
メモリアドレス
メモリアドレスに対するスレッドのアクセス要求
読込に必要な32バイトトランザクション
32バイトのメモリセグメント読み込むデータ
キャッシュされない読込の例
2015/05/07先端GPGPUシミュレーション工学特論26
アライン/アンコアレスアクセス
Warp内の全スレッドが要求するメモリアドレスが128バイト(四つのセグメントに収まる)
各スレッドがアクセスするメモリアドレスが不連続
先頭データのメモリアドレスが32バイトの倍数
読込に必要な32バイトのトランザクションは四つ
読み込んだデータ全てを利用
バスの利用率は128/(32×4)=100%
012345678910111213141516171819202122232425262728293031
0 16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
256
272
288
304
320
336
352
358
キャッシュされない読込の例
2015/05/07先端GPGPUシミュレーション工学特論27
ミスアライン/コアレスアクセス
Warpがアクセスするデータの先頭アドレスが128の倍数ではない(五つのセグメントにまたがる)
全スレッドが隣り合ったメモリにアクセス
先頭データのメモリアドレスが32バイトの倍数ではない
読込に必要な32バイトのトランザクションは五つ
読み込んだデータの8割は利用
バスの利用率は128/(32×5)=80% 改善!
012345678910111213141516171819202122232425262728293031
0 16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
256
272
288
304
320
336
352
358
キャッシュされない読込の例
2015/05/07先端GPGPUシミュレーション工学特論28
ミスアライン/アンコアレスアクセス
Warp内の全スレッドが同じアドレスにアクセス要求
一つのセグメントに収まる
読込に必要な32バイトのトランザクションは一つ
読み込んだ32バイトのうち利用されるのは4バイトのみ
バスの利用率は4/32=12.5% 改善!
012345678910111213141516171819202122232425262728293031
0 16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
256
272
288
304
320
336
352
358
キャッシュされない読込の例
2015/05/07先端GPGPUシミュレーション工学特論29
ミスアライン/アンコアレスアクセス
Warp内の各スレッドが広範囲に点在するデータにアクセス
複数のセグメントにまたがる(上の例では10個)
読込に必要な32バイトのトランザクションは 悪で32個 悪でも32バイトのセグメント32個に分散
バスの利用率は 悪で128/(32×32)=12.5% 改善!上の例では128/(32×10)=40% 改善!
何度も読み込むための待ちは依然として発生
012345678910111213141516171819202122232425262728293031
0 16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
256
272
288
304
320
336
352
358
L2キャッシュ
コンスタントメモリ
テクスチャメモリ
GPU
レジスタ
レジスタ
レジスタ
レジスタ
CUDA Core
CUDA Core
CUDA Core
CUDA Core
L1キャッシュ
共有メモリ
SM
レジスタ
レジスタ
レジスタ
レジスタ
CUDA Core
CUDA Core
CUDA Core
CUDA Core
L1キャッシュ
共有メモリ
SM
グローバルメモリ
ローカルメモリ
ローカルメモリ
ローカルメモリ
ローカルメモリ
・・・
・・・
Chip
共有(シェアード)メモリ
ブロック内のスレッドが共通のデータ(メモリアドレス)にアクセス可能
物理的にSMに近い
遅延はグローバルメモリの20~30分の1,帯域幅は10倍
2015/05/07先端GPGPUシミュレーション工学特論30
Fermi世代以前のGPUでマネージドキャッシュとして利用
1ブロックあたり16,32*,48kB
*Kepler世代から
ホストメモリ
共有(シェアード)メモリ
修飾子 __shared__ を付けて宣言
データをブロック内で共有するために同期を取る
共有メモリ上でデータを変更してもグローバルメモリには反映されない グローバルメモリへの書き戻しが必要
2015/05/07先端GPGPUシミュレーション工学特論31
__global__ void kernel(flaot *a){__shared__ float shared_a[NT];//NTはスレッド数int i = blockDim.x*blockIdx.x + theadIdx.x;
shared_a[threadIdx.x] = a[i];__syncthreads();//共有メモリを使った処理__syncrthreads();a[i] = shared_a[threadIdx.x];
}
共有(シェアード)メモリ利用のイメージ
2015/05/07先端GPGPUシミュレーション工学特論32
a[i]
a[i]
shared_a[tx]
i= 0 1 2 3 4 5 6 7tx=threadIdx.x= 0 1 2 3 0 1 2 3
tx= 0 1 2 3 0 1 2 3ブロック内で同期
共有メモリを使った処理
shared_a[tx]
blockIdx.x=0 blockIdx.x=1
共有メモリを使った処理
一つのブロック内で使用する分のみを確保
同期を取り,共有メモリ内に正しくデータが入っていることを保証ブロック内で同期
処理
の流
れ
ブロック内でのスレッドの同期
2015/05/07先端GPGPUシミュレーション工学特論33
__syncthreads(); カーネル実行中にスレッドの同期を取る
__syncthreads()が書かれた行にスレッドが到達する
と,同一ブロック内の他の全てのスレッドがその行に達するまで待機
異なるブロック間での同期は不可能
1ブロックあたりのスレッド数が32以下では不要*
32スレッドが一つのwarpとして協調して処理を実行するため
ifの中に記述するとカーネルが終了しないことがあるif(条件){
__syncthreads();//条件が真にならないスレッドはifの中に入らないため,//カーネルが永久に終わらない
}
*Warp同期プログラミングは将来のGPUでサポートが廃止される予定
共有(シェアード)メモリの宣言
修飾子 __shared__ を付けて宣言
配列として宣言 要素数を静的(コンパイル時)に決定する場合
__shared__ 型 変数名[要素数] 多次元配列も宣言可能(1,2,3次元)
要素数を動的(カーネル実行時)に決定する場合
extern __shared__ 型 変数名[] メモリサイズをカーネル呼出時のパラメータで指定
<<<ブロック数,スレッド数,共有メモリのサイズ>>>
2015/05/07先端GPGPUシミュレーション工学特論34=要素数*sizeof(共有メモリの型)
共有(シェアード)メモリ容量の選択
2015/05/07先端GPGPUシミュレーション工学特論35
共有メモリとL1キャッシュは64kBを共用 どちらを多く利用するかを決定する関数が用意されている
64kB全ての利用は不可能
L1キャッシュ48kB/共有メモリ16kB cudaDeviceSetCacheConfig(cudaFuncCachePreferL1);
L1キャッシュ16kB/共有メモリ48kB cudaDeviceSetCacheConfig(cudaFuncCachePreferShared); コンパイルオプションとして ‐Xptxas ‐dlcm=cgを与えるとL1キャッシュを利用しなくなるが,48kB以上は利用できない
L1キャッシュ32kB/共有メモリ32kB* cudaDeviceSetCacheConfig(cudaFuncCachePreferEqual);
*Kepler世代から
カーネル単位での共有メモリ容量の選択
2015/05/07先端GPGPUシミュレーション工学特論36
デバイス(GPU)単位ではなくカーネル単位で共有メモリの容量を決定する関数も用意 cudaFuncSetCacheConfig(関数ポインタ,容量の設定) 容量の設定はcudaDeviceSetCacheConfigと同じ
cudaFuncCachePreferL1 cudaFuncCachePreferShared cudaFuncCachePreferEqual* cudaFuncCachePreferNone(デフォルト)
カーネルごとに一度設定するだけでよい
デバイスごとの設定(cudaDeviceSetCacheConfig)を上書きできる
*Kepler世代から
共有(シェアード)メモリへのアクセス(Tesla世代)
高速にアクセスするための制約が緩い
16スレッド(Half Warp)単位でアクセス
16個のバンクから構成 32bit=4バイトが1バンク
一つのバンクにアクセスできるのはHalf Warp内の1スレッドのみ 複数のスレッドが一つのバンクに同時にアクセスするとバンクコンフリクト(バンク衝突)が発生
共有メモリへのアクセスが逐次化される2015/05/07先端GPGPUシミュレーション工学特論37
バンクコンフリクトにならない例(Tesla世代)
Half Warpが異なるバンクにアクセス
隣接データにアクセス
データのサイズは32bit (float型)
コアレスアクセスに類似
Half Warpが異なるバンクにアクセス
ランダムアクセス
データのサイズは32bit
アドレスが不連続でもよい
コアレスアクセスと異なる
15
14
13
12
11
10
9
8
7
6
5
4
3
2
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1 1
0 0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1 1
0 0
2015/05/07先端GPGPUシミュレーション工学特論38
メモリアドレスに対するスレッドのアクセス要求 Half Warp内でのスレッドID
バンク番号
96bit(12バイト)ごとにアクセス float型データ3個分の構造体など
同じバンクにはアクセスしていない
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
64
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
128
バンクコンフリクトにならない例(Tesla世代)
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
0
2015/05/07先端GPGPUシミュレーション工学特論39
メモリアドレスに対するスレッドのアクセス要求
メモリアドレス
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
バンクコンフリクトする例(Tesla世代)
64bit(8バイト)ごとにアクセス double型では必ずバンクコンフリクトが発生
2wayバンクコンフリクト スレッド0と8, 1と9,2と10...は同時に共有メモリにアクセスできない
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
64
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
0
2015/05/07先端GPGPUシミュレーション工学特論40
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンクコンフリクト
バンクコンフリクト
バンクコンフリクト
バンクコンフリクト
バンクコンフリクト
バンクコンフリクト
バンクコンフリクト
バンクコンフリクト
バンク番号
バンクコンフリクトする例(Tesla世代)
256bit(32バイト)ごとにアクセス
8wayバンクコンフリクト
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
64
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
128
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
0
252
248
244
240
236
232
228
224
220
216
212
208
204
200
196
192
2015/05/07先端GPGPUシミュレーション工学特論41
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンクコンフリクト
バンクコンフリクト
バンク番号
バンクコンフリクトしない例(Tesla世代)
16スレッドが一つの
バンクの同一アドレスにアクセス
配列のある要素のデータを全スレッドが共有
バンクコンフリクトは発生しない
ブロードキャストが行われる
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
2015/05/07先端GPGPUシミュレーション工学特論42
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
共有(シェアード)メモリへのアクセス(Fermi世代以降)
32スレッド(1 Warp)単位でアクセス
32個のバンクから構成 Compute Capabilityが2.x 32bit=4バイトが1バンク
帯域幅はクロックサイクルふたつあたり32ビット
Compute Capabilityが3.x 64bit=8バイトが1バンク
帯域幅はクロックサイクルあたり64ビット
バンクコンフリクトはFermiと同じか少ない
関数でバンクサイズを設定可能 cudaDeviceSetShareMemConfig(設定);設定 cudaSharedMemBankSizeFourByte 4バイト cudaSharedMemBankSizeEightByte 8バイト
2015/05/07先端GPGPUシミュレーション工学特論43
128
バンクコンフリクトにならないアクセス(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論44
Warp内の32スレッドが異なるバンクにある32ビットのデータに隣接アクセス 理想的なアクセスパターン
Warpが発行した共有メモリの読込,書込命令が一つのトランザクションで処理される
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
バンク番号 0 1 2 3
144
4 5 6 7
160
8 9 10 11
176
12 13 14 15
192
16 17 18 19
208
20 21 22 23
224
24 25 26 27
240
28 29 30 31
バンクコンフリクトにならないアクセス(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論45
Warp内の32スレッドが異なるバンクにある32ビットのデータにランダムアクセス 各スレッドは異なるバンクにアクセス
バンクコンフリクトは発生しない
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
128
0 1 2 3
144
4 5 6 7
160
8 9 10 11
176
12 13 14 15
192
16 17 18 19
208
20 21 22 23
224
24 25 26 27
240
28 29 30 31バンク番号
バンクコンフリクトにならないアクセス(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論46
複数のスレッドが一つのバンクの同一アドレスにアクセス バンクコンフリクトは発生しない
メモリトランザクションが一つ実行され,アクセスした複数のスレッドに値がブロードキャストされる
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
128
0 1 2 3
144
4 5 6 7
160
8 9 10 11
176
12 13 14 15
192
16 17 18 19
208
20 21 22 23
224
24 25 26 27
240
28 29 30 31バンク番号
バンクコンフリクトになるアクセス(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論47
複数のスレッドが一つのバンクの異なるアドレスにアクセス バンクコンフリクトが発生(下の例では4‐way) メモリアクセスが逐次化( 悪で32倍の時間がかかる)
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0
128
256
384
0 1 2 3
16
144
272
400
4 5 6 7
32
160
288
416
8 9 10 11
48
176
304
432
12 13 14 15
64
192
320
448
16 17 18 19
80
208
336
464
20 21 22 23
96
224
352
480
24 25 26 27
112
240
358
496
28 29 30 31バンク番号
バンクコンフリクト
バンクコンフリクト
バンクコンフリクト
レジスタ
各スレッドが個別に利用
カーネル内で変数を宣言するとレジスタを利用
非常に高速 キャッシュとしても利用可能
2015/05/07先端GPGPUシミュレーション工学特論48*Keplerからは65536本
少容量 32768本*×32bit 利用可能分を超えるとローカルメモリへ追い出される
レジスタスピル
L2キャッシュ
コンスタントメモリ
テクスチャメモリ
GPU
レジスタ
レジスタ
レジスタ
レジスタ
CUDA Core
CUDA Core
CUDA Core
CUDA Core
L1キャッシュ
共有メモリ
SM
レジスタ
レジスタ
レジスタ
レジスタ
CUDA Core
CUDA Core
CUDA Core
CUDA Core
L1キャッシュ
共有メモリ
SM
グローバルメモリ
ローカルメモリ
ローカルメモリ
ローカルメモリ
ローカルメモリ
・・・
・・・
Chip
ホストメモリ
並列化の階層とメモリの階層
並列化の階層 メモリの階層
Grid
Block
Thread
Warp
グローバルメモリ
共有メモリ
GPU
Streaming Multiproc
essor
CUDA Core
オフチップメモリ
オンチップメモリ
ハードウェア構成
ハードウェア構成
レジスタ
2015/05/07先端GPGPUシミュレーション工学特論49
共有メモリの使いどころ
2015/05/07先端GPGPUシミュレーション工学特論50
Tesla世代以前では共有メモリの利用は必須 キャッシュを未搭載
同じデータに2回以上アクセスする場合は共有メモリに保持して再利用
Fermi世代以降 キャッシュを搭載
単純な処理ではキャッシュが大いに活躍
プログラマが管理できるキャッシュ
キャッシュされない条件下で共有メモリを利用してデータを再利用
メモリアクセスパターンの 適化
グローバルメモリへのアクセスの制約を回避
配列(画像や行列)の転置
2015/05/07先端GPGPUシミュレーション工学特論51
読込と書込で配列へのアクセスパターンが異なる
一方はコアレッシング可,一方はコアレッシング不可
共有メモリを利用してメモリアクセスを改善
1 2
3 4
1 3
2 4j
i
j
i
配列(画像や行列)の転置
2015/05/07先端GPGPUシミュレーション工学特論52
2次元配列の転置
out[j][i] = in[i][j]
in[][] out[][]
Ny
Nx
Nx
Ny
配列(画像や行列)の転置
2015/05/07先端GPGPUシミュレーション工学特論53
1次元配列に保存されたデータの転置
out[i*Ny+j] = in[j*Nx+i]
Ny
Nx
Nx
Ny
in[] out[]
#include<stdio.h>#include<stdlib.h>#define Nx 4096#define Ny 2048#define Nbytes (Nx*Ny*sizeof(int))
void init(int *in, int *out){int i,j;for(i=0; i<Nx; i++){for(j=0; j<Ny; j++){
in[i*Ny + j] = j;out[i*Ny + j] = ‐1;
}}
}
void transpose(int *in, int *out){int i,j;for(i=0; i<Nx; i++){
for(j=0; j<Ny; j++){out[j*Nx+i] = in[i*Ny+j];
}}
}
int main(){int *in,*out;
in = (int *)malloc(Nbytes);out = (int *)malloc(Nbytes);
init(in,out);transpose(in,out);
return 0;}
CPUプログラム
2015/05/07先端GPGPUシミュレーション工学特論54
transpose.c
GPUへの移植
2015/05/07先端GPGPUシミュレーション工学特論55
2次元的に並列化し,1スレッドが1要素を転置
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=
1スレッドが読み込む配列要素の決定
2015/05/07先端GPGPUシミュレーション工学特論56
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スレッドが書き込む配列要素の決定
2015/05/07先端GPGPUシミュレーション工学特論57
グローバルメモリへの書込は,読込の際に設定したブロックやスレッドの配置に制限されない スレッド番号と配列添字を対応付ける便宜的な概念
in[ij] out[ij]
#include<stdio.h>#define Nx 4096#define Ny 2048#define Nbytes (Nx*Ny*sizeof(int))#define Tx 16#define Ty 16
__global__ void init(int *in, int *out){
int i = blockIdx.x*blockDim.x+ threadIdx.x;
int j = blockIdx.y*blockDim.y+ threadIdx.y;
in[i*Ny + j] = j;out[i*Ny + j] = ‐1;
}__global__ void transpose(int *in, int *out){
int i = blockIdx.x*blockDim.x
+ threadIdx.x;int j = blockIdx.y*blockDim.y
+ threadIdx.y;out[j*Nx + i] = in[i*Ny + j];
}
int main(){int *in,*out;dim3 Thread(Tx,Ty,1),
Block(Nx/Tx,Ny/Ty,1);
cudaMalloc((void**)&in,Nbytes);cudaMalloc((void**)&out,Nbytes);init<<<Block, Thread>>>(in,out); transpose<<<Block, Thread>>>
(in,out);return 0;
}
GPUプログラム
2015/05/07先端GPGPUシミュレーション工学特論58
simple_transpose.cu
2次元的な配列アクセスの優先方向
2015/05/07先端GPGPUシミュレーション工学特論59
x, yのどちらをメモリが連続な方向とするか threadIdx.xが連続な方向にアクセスする場合
(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) threadIdx.x=0~15,threadIdx.y=0
threadIdx.x=0~15,threadIdx.y=1...
[i][j]=[j*Nx+i]
メモリが連続な方向
thre
adId
x.y
threadIdx.x
2次元的な配列アクセスの優先方向
2015/05/07先端GPGPUシミュレーション工学特論60
x, 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) threadIdx.x=0, threadIdx.y=0~15
threadIdx.x=1, threadIdx.y=0~15...
[i][j]=[i*Ny+j]
メモリが連続な方向
thre
adId
x.y
threadIdx.x
#include<stdio.h>#define Nx 4096#define Ny 2048#define Nbytes (Nx*Ny*sizeof(int))#define Tx 16#define Ty 16
__global__ void init(int *in, int *out){
int i = blockIdx.x*blockDim.x+ threadIdx.x;
int j = blockIdx.y*blockDim.y+ threadIdx.y;
in[i*Ny + j] = j;out[i*Ny + j] = ‐1;
}__global__ void copy(int *in, int *out){
int i = blockIdx.x*blockDim.x
+ threadIdx.x;int j = blockIdx.y*blockDim.y
+ threadIdx.y;out[j*Nx + i] = in[j*Nx + i];out[i*Ny + j] = in[i*Ny + j];
}
int main(){int *in,*out;dim3 Thread(Tx,Ty,1),
Block(Nx/Tx,Ny/Ty,1);
cudaMalloc((void**)&in,Nbytes);cudaMalloc((void**)&out,Nbytes);init<<<Block, Thread>>>(in,out);copy<<<Block, Thread>>>(in,out);return 0;
}
2次元的な配列アクセスの優先方向
2015/05/07先端GPGPUシミュレーション工学特論61
simple_copy.cu
どちらが高速?
実行時間
2015/05/07先端GPGPUシミュレーション工学特論62
入力配列サイズ Nx×Ny = 4096×2048 スレッド数 Tx×Ty = 16×16
カーネル 実行時間 [ms]
copy(目標値) 0.802 / 1.57
2次元的な配列アクセスの優先方向
2015/05/07先端GPGPUシミュレーション工学特論63
2次元配列の場合
in[][],out[][]
Nx
Ny
j
ifor(i=0;i<Nx;i++)
for(j=0;j<Ny;j++)out[i][j]=in[i][j];
for(j=0;j<Ny;j++)for(i=0;i<Nx;i++)
out[i][j]=in[i][j];
2次元的な配列アクセスの優先方向
2015/05/07先端GPGPUシミュレーション工学特論64
2次元配列の場合
for(i=0;i<Nx;i++)for(j=0;j<Ny;j++)
out[i][j]=in[i][j];
in[][],out[][]
Nx
Ny
j
i
for(j=0;j<Ny;j++)for(i=0;i<Nx;i++)
out[i][j]=in[i][j];
2次元的な配列アクセスの優先方向
2015/05/07先端GPGPUシミュレーション工学特論65
2次元配列の1次元配列的表現(前回資料参照)
for(i=0;i<Nx;i++)for(j=0;j<Ny;j++)
out[i][j]=in[i][j];
in[],out[]
Nx
Ny
j
i
for(i=0;i<Nx;i++)for(j=0;j<Ny;j++)
out[i*Ny+j]=in[i*Ny+j];
2次元的な配列アクセスの優先方向
2015/05/07先端GPGPUシミュレーション工学特論66
CUDAで2次元的に並列化してアクセスする場合
i = blockIdx.x*blockDim.x+ threadIdx.x;
j = blockIdx.y*blockDim.y+ threadIdx.y;
out[j*Nx+i]=in[j*Nx+i];
in[],out[]
Nx
Ny
j
ifor(i=0;i<Nx;i++)
for(j=0;j<Ny;j++)out[i*Ny+j]=
in[i*Ny+j];
threadIdx.x
thre
adId
x.y
threadIdx.xのスレッド群(threadIdx.yが一定)
が連続なメモリアドレスにアクセス
#include<stdio.h>#define Nx 4096#define Ny 2048#define Nbytes (Nx*Ny*sizeof(int))#define Tx 16#define Ty 16
__global__ void init(int *in, int *out){
int i = blockIdx.x*blockDim.x+ threadIdx.x;
int j = blockIdx.y*blockDim.y+ threadIdx.y;
in[j*Nx + i] = j;out[j*Nx + i] = ‐1;
}__global__ void transpose(int *in, int *out){
int i = blockIdx.x*blockDim.x
+ threadIdx.x;int j = blockIdx.y*blockDim.y
+ threadIdx.y;out[i*Ny + j] = in[j*Nx + i];
}
int main(){int *in,*out;dim3 Thread(Tx,Ty,1),
Block(Nx/Tx,Ny/Ty,1);
cudaMalloc((void**)&in,Nbytes);cudaMalloc((void**)&out,Nbytes);init<<<Block, Thread>>>(in,out); transpose<<<Block, Thread>>>
(in,out);return 0;
}
GPUプログラム
2015/05/07先端GPGPUシミュレーション工学特論67
transpose.cu
実行時間
2015/05/07先端GPGPUシミュレーション工学特論68
入力配列サイズ Nx×Ny = 4096×2048 スレッド数 Tx×Ty = 16×16
カーネル 実行時間 [ms]
copy(目標値) 0.802 / 1.57
transpose 1.34
転置時のメモリアクセス
2015/05/07先端GPGPUシミュレーション工学特論69
読込はコアレスアクセス
書込はストライドアクセス キャッシュはメモリアクセスの改善には利用できない
in[]
out[]
out[i*Ny + j] = in[j*Nx + i];
・・・ ・・・ ・・・ ・・・ ・・・ ・・・ ・・・
・・・ ・・・ ・・・ ・・・ ・・・ ・・・ ・・・
Ny*sizeof(int)バイトのストライドアクセス
転置時のメモリアクセス
2015/05/07先端GPGPUシミュレーション工学特論70
読込はコアレスアクセス
書込は非コアレスアクセス キャッシュはメモリアクセスの改善には利用できない
in[] out[]
threadIdx.xの群が連続
なメモリアドレスにアクセス
out[i*Ny + j] = in[j*Nx + i];
Ny
Nx
Nx
Ny
threadIdx.yの群が連続
なメモリアドレスにアクセスth
read
Idx.
y
threadIdx.y
thre
adId
x.x
threadIdx.x
in[]
共有メモリによるメモリアクセスの改善
2015/05/07先端GPGPUシミュレーション工学特論71
共有メモリは制約が緩い 一度グローバルメモリから共有メモリにコピー
ここは元々コアレスアクセス
out[]
thre
adId
x.y
threadIdx.x
threadIdx.x
thre
adId
x.y
in[]
共有メモリによるメモリアクセスの改善
2015/05/07先端GPGPUシミュレーション工学特論72
共有メモリは制約が緩い 共有メモリ内で転置
共有メモリからグローバルメモリへコアレスアクセス
out[]
threadIdx.y
thre
adId
x.x
threadIdx.x
thre
adId
x.y
グローバルメモリから共有メモリへの書込
2015/05/07先端GPGPUシミュレーション工学特論73
__shared__ int data[Tx][Ty]; //ブロック内のスレッドが使うサイズだけ宣言int i = blockIdx.x*blockDim.x + threadIdx.x; //配列添字とスレッド番号のint j = blockIdx.y*blockDim.y + threadIdx.y; //対応を計算
原点からのズレ//グローバルメモリから共有メモリへコピー(コアレスアクセス)data[threadIdx.x][threadIdx.y] = in[j*Nx + i];__syncthreads(); //ブロック内の全スレッドがコピーを完了するのを待機
(0,0)
(0,1)
(0,2)
(0,3)
(1,0)
(1,1)
(1,2)
(1,3)
(2,0)
(2,1)
(2,2)
(2,3)
(3,0)
(3,1)
(2,3)
(3,3)
(0,0)
(1,0)
(2,0)
(3,0)
(0,1)
(1,1)
(2,1)
(3,1)
(0,2)
(1,2)
(2,2)
(3,2)
(0,3)
(1,3)
(2,3)
(3,3)
グローバルメモリから読み込んだデータをそのまま共有メモリへ書込み
blockIdx.x*blockDim.x=1*4
blockIdx.y*blockDim.y=0*4 threadIdx.x
thre
adId
x.y
threadIdx.x
thre
adId
x.y
共有メモリからグローバルメモリへの書込
2015/05/07先端GPGPUシミュレーション工学特論74
i = blockIdx.y*blockDim.y + threadIdx.x;j = blockIdx.x*blockDim.x + threadIdx.y;
原点からのズレ配列out[]の横幅(コピー時の書き方out[j*Nx + i] = in[j*Nx + i];と同じ)
out[j*Ny + i] = data[threadIdx.y][threadIdx.x];
(0,0)(1,0)(2,0)(3,0)
(0,1)(1,1)(2,1)(3,1)
(0,2)(1,2)(2,2)(3,2)
(0,3)(1,3)(2,3)(3,3)
(0,0)
(1,0)
(2,0)
(3,0)
(0,1)
(1,1)
(2,1)
(3,1)
(0,2)
(1,2)
(2,2)
(3,2)
(0,3)
(1,3)
(2,3)
(3,3)
各スレッドが共有メモリへアクセスする位置を転置
blockIdx.y*blockDim.y=0*4
blockIdx.x*blockDim.x=1*4
Ny
threadIdx.x
thre
adId
x.y
threadIdx.y
thre
adId
x.xthreadIdx.x,
threadIdx.y が
参照する次元が入れ替わっている
__global__ void transpose_shared(int *in, int *,out){__shared__ int data[Tx][Ty]; //ブロック内のスレッドが使うサイズだけ宣言int i = blockIdx.x*blockDim.x + threadIdx.x; //配列添字とスレッド番号のint j = blockIdx.y*blockDim.y + threadIdx.y; //対応を計算
//グローバルメモリから共有メモリへコピー(コアレスアクセス)data[threadIdx.x][threadIdx.y] = in[j*Nx + i];__syncthreads(); //ブロック内の全スレッドがコピーを完了するのを待機
i = blockIdx.y*blockDim.y + threadIdx.x;j = blockIdx.x*blockDim.x + threadIdx.y;out[j*Ny + i] = data[threadIdx.y][threadIdx.x];
}
共有メモリを利用するカーネル
2015/05/07先端GPGPUシミュレーション工学特論75
transpose_shared.cu
実行時間
2015/05/07先端GPGPUシミュレーション工学特論76
入力配列サイズ Nx×Ny = 4096×2048 スレッド数 Tx×Ty = 16×16
1.4倍高速化*
*高速化率=基準となる実行時間/高速化したカーネルによる実行時間
カーネル 実行時間 [ms]
copy(目標値) 0.802 / 1.57
transpose 1.34
transpose_shared 0.977
2次元配列におけるバンクコンフリクト(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論77
転置の計算
共有メモリにアクセスする添字が入れ替わる
読込か書込のどちらかでバンクコンフリクトが発生
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
64
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
128
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
0
256
252
248
244
240
236
232
228
224
220
216
208
204
200
196
192
data[16][16]
・・・
Half Warp内でのスレッドID メモリアドレス
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
64
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
128
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
0
256
252
248
244
240
236
232
228
224
220
216
208
204
200
196
192
2次元配列におけるバンクコンフリクト(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論78
transpose_shared.cuの場合 読込のアクセス
書込のアクセス
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
//16‐wayバンクコンフリクトdata[threadIdx.x][threadIdx.y]
・・・
バンクコンフリクト
data[threadIdx.x][0]
data[threadIdx.y][threadIdx.x]
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
256
252
248
244
240
236
232
228
224
220
216
208
204
200
196
2次元配列におけるバンクコンフリクト(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論79
transpose_shared.cuの場合 読込のアクセス
書込のアクセス
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
//16‐wayバンクコンフリクトdata[threadIdx.x][threadIdx.y]
・・・
バンクコンフリクト
data[threadIdx.y][threadIdx.x]
64 1280 192
data[threadIdx.x][1]
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
64
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
128
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
0
256
252
248
244
240
236
232
228
224
220
216
208
204
200
196
192
2次元配列におけるバンクコンフリクト(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論80
transpose_shared.cuの場合 読込のアクセス
書込のアクセス
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
data[threadIdx.x][threadIdx.y]
・・・
data[0][threadIdx.x]
//コンフリクトフリーdata[threadIdx.y][threadIdx.x]
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
256
252
248
244
240
236
232
228
224
220
216
208
204
200
196
2次元配列におけるバンクコンフリクト(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論81
transpose_shared.cuの場合 読込のアクセス
書込のアクセス
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
・・・
64 1280 192
data[1][threadIdx.x]
data[threadIdx.x][threadIdx.y]
//コンフリクトフリーdata[threadIdx.y][threadIdx.x]
バンクコンフリクトの低減(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論82
解消は非常に簡単
配列を余分に確保してバンクをずらす
data[16][16]
data[16__][16__]
210
15
14
13
12
11
10
9
8
7
6
5
4
3
2
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1 1
0 0
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
1
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
…
メモリアドレスに対するスレッドのアクセス要求
バンク番号
バンクコンフリクトの低減(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論83
解消は非常に簡単
配列を余分に確保してバンクをずらす
data[16][16]
data[16 ][16+1] 配列を余分に確保することをパディング(詰め物)するという
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
…
210
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
1
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1 2 30
バンクコンフリクトの低減(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論84
メモリパディングをした場合 読込のアクセス
書込のアクセス・・・
メモリアドレスに対するスレッドのアクセス要求
//コンフリクトフリーdata[threadIdx.x][threadIdx.y]
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
1
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
2
1
0
15
14
13
12
11
10
9
8
7
6
5
4
3
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
data[threadIdx.x][0]
data[threadIdx.y][threadIdx.x]
バンクコンフリクトの低減(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論85
メモリパディングをした場合 読込のアクセス
書込のアクセス
メモリアドレスに対するスレッドのアクセス要求 1 2 30
・・・
//コンフリクトフリーdata[threadIdx.x][threadIdx.y]
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
1
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
2
1
0
15
14
13
12
11
10
9
8
7
6
5
4
3
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
data[threadIdx.x][1]
data[threadIdx.y][threadIdx.x]
バンクコンフリクトの低減(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論86
メモリパディングをした場合 読込のアクセス
書込のアクセス
メモリアドレスに対するスレッドのアクセス要求 1 2 30
・・・
data[threadIdx.x][threadIdx.y]
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
1
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
2
1
0
15
14
13
12
11
10
9
8
7
6
5
4
3
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
data[0][threadIdx.x]
//コンフリクトフリーdata[threadIdx.y][threadIdx.x]
バンクコンフリクトの低減(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論87
メモリパディングをした場合 読込のアクセス
書込のアクセス
メモリアドレスに対するスレッドのアクセス要求 1 2 30
・・・
data[threadIdx.x][threadIdx.y]
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
1
0
15
14
13
12
11
10
9
8
7
6
5
4
3
2
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
2
1
0
15
14
13
12
11
10
9
8
7
6
5
4
3
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
data[1][threadIdx.x]
//コンフリクトフリーdata[threadIdx.y][threadIdx.x]
2次元配列におけるバンクコンフリクト(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論88
バンクの数が32に増加
配列要素数に応じてバンクが折りたたまれる
配列の2次元の要素数とバンクの数が同じ場合
0
128
256
384
0 1 2 3
16
144
272
400
4 5 6 7
32
160
288
416
8 9 10 11
48
176
304
432
12 13 14 15
data[8][32]
…
data[0][32]
data[1][32]
data[2][32]
data[3][32]
64
192
320
448
16 17 18 19
80
208
336
464
20 21 22 23
96
224
352
480
24 25 26 27
112
240
358
496
28 29 30 31
メモリアドレス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
バンク番号
Warp内でのスレッドID
2次元配列におけるバンクコンフリクト(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論89
バンクの数が32に増加
配列要素数に応じてバンクが折りたたまれる
配列の2次元の要素数とバンクの数が異なる場合
…
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16]
0
128
64
192
16
144
80
208
32
160
96
224
48
176
112
240
data[0][16]
data[1][16]
data[2][16]
data[3][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
2次元配列におけるバンクコンフリクト(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論90
バンクの数が32に増加
配列要素数に応じてバンクが折りたたまれる
配列の2次元の要素数とバンクの数が異なる場合
…
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16]
data[0][16]
data[1][16]
data[2][16]
data[3][16]
バンク番号
2次元的なスレッドとWarpの対応(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論91
1ブロック内のスレッドはWarp単位で実行 32個の連続したスレッド
1ブロック内にスレッドが2次元的に配置されていても,ハードウェア的には1次元的に配置
ブロック内のスレッド番号とWarpの対応 threadIdx.xの値が 初に変化( も内側)
threadIdx.xがカーネル起動時に設定された数字を超えるとthreadIdx.yの値が変化
スレッド数を(16,16,1)として起動した場合
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
00
10
20
30
40
50
60
70
80
90100110120130140150
01
11
21
31
41
51
61
71
81
91101111121131141151
threadIdx.xthreadIdx.y
・・・0 1 2 3 4 5 6 7
02
12
22
32
42
52
62
72 ・・・
Warp 0 Warp 1
Warp内でのスレッドID
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
2次元配列におけるバンクコンフリクト(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論92
バンクの数が32に増加
配列要素数に応じてバンクが折りたたまれる
配列の2次元の要素数とバンクの数が異なる場合0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16]
data[0][16]
data[1][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15data[2][16]
data[3][16]
コンフリクトフリー
…
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
2次元配列におけるバンクコンフリクト(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論93
転置の計算 共有メモリにアクセスする添字が入れ替わる
読込か書込のどちらかでバンクコンフリクトが発生
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16]
data[0][16]
data[1][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15data[2][16]
data[3][16] …
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
2次元配列におけるバンクコンフリクト(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論94
transpose_shared.cuの場合 読込のアクセス
書込のアクセス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16] data[0][16]
data[1][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15data[2][16]
data[3][16]
//8‐wayバンクコンフリクトdata[threadIdx.x][threadIdx.y]
data[threadIdx.y][threadIdx.x]
…
data[threadIdx.x][0] data[threadIdx.x][1]
バンクコンフリクト
バンクコンフリクト
00
10
20
30
40
50
60
70
80
90100110120130140150
01
11
21
31
41
51
61
71
81
91101111121131141151
threadIdx.xthreadIdx.y
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
2次元配列におけるバンクコンフリクト(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論95
transpose_shared.cuの場合 読込のアクセス
書込のアクセス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16] data[0][16]
data[1][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15data[2][16]
data[3][16]
//8‐wayバンクコンフリクトdata[threadIdx.x][threadIdx.y]
…
data[threadIdx.x][2] data[threadIdx.x][3]
バンクコンフリクト
バンクコンフリクト
data[threadIdx.y][threadIdx.x]
00
10
20
30
40
50
60
70
80
90100110120130140150
01
11
21
31
41
51
61
71
81
91101111121131141151
threadIdx.xthreadIdx.y
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
2次元配列におけるバンクコンフリクト(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論96
transpose_shared.cuの場合 読込のアクセス
書込のアクセス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16] data[0][16]
data[1][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15data[2][16]
data[3][16]
data[threadIdx.x][threadIdx.y]
//コンフリクトフリーdata[threadIdx.y][threadIdx.x]
…
data[0][threadIdx.x] data[1][threadIdx.x]
00
10
20
30
40
50
60
70
80
90100110120130140150
01
11
21
31
41
51
61
71
81
91101111121131141151
threadIdx.xthreadIdx.y
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
2次元配列におけるバンクコンフリクト(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論97
transpose_shared.cuの場合 読込のアクセス
書込のアクセス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16] data[0][16]
data[1][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15data[2][16]
data[3][16]
data[threadIdx.x][threadIdx.y]
…
data[2][threadIdx.x] data[3][threadIdx.x]
//コンフリクトフリーdata[threadIdx.y][threadIdx.x]
00
10
20
30
40
50
60
70
80
90100110120130140150
01
11
21
31
41
51
61
71
81
91101111121131141151
threadIdx.xthreadIdx.y
バンクコンフリクトの低減(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論98
配列を余分に確保してバンクをずらす data[16][16]
data[16][16+1]
data[16][17]
0 16 32 48 64
追加分の配列要素
80 96 112
128
144
160
176
192
208
224
240
256
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
…
data[0][17]
data[1][17]
data[2][17]
data[3][17]
19 20 21 22 23 24 25 26 27 28 29 30 31 0 1
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0 1
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
バンクコンフリクトの低減(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論99
配列を余分に確保してバンクをずらす data[16][16]
data[16][16+1]
data[16][17]
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
1 2 3
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
…
data[0][17]
data[1][17]
data[2][17]
data[3][17]
19 20 21 22 23 24 25 26 27 28 29 30 31 0 11 2 3
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0 1
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
バンクコンフリクトの低減(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論100
メモリパディングをした場合 読込のアクセス
書込のアクセス
//コンフリクトフリーdata[threadIdx.x][threadIdx.y]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][17] data[0][17]
data[1][17]
data[2][17]
data[3][17]
data[threadIdx.x][0] data[threadIdx.x][1]
…
00
10
20
30
40
50
60
70
80
90100110120130140150
threadIdx.xthreadIdx.y
01
11
21
31
41
51
61
71
81
91101111121131141151
data[threadIdx.y][threadIdx.x]
19 20 21 22 23 24 25 26 27 28 29 30 31 0 11 2 3
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0 1
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
バンクコンフリクトの低減(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論101
メモリパディングをした場合 読込のアクセス
書込のアクセス
//コンフリクトフリーdata[threadIdx.x][threadIdx.y]
data[threadIdx.y][threadIdx.x]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][17] data[0][17]
data[1][17]
data[2][17]
data[3][17]
data[threadIdx.x][2] data[threadIdx.x][3]
…
00
10
20
30
40
50
60
70
80
90100110120130140150
threadIdx.xthreadIdx.y
01
11
21
31
41
51
61
71
81
91101111121131141151
19 20 21 22 23 24 25 26 27 28 29 30 31 0 11 2 3
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0 1
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
バンクコンフリクトの低減(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論102
メモリパディングをした場合 読込のアクセス
書込のアクセス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][17] data[0][17]
data[1][17]
data[2][17]
data[3][17]
data[threadIdx.x][threadIdx.y]
//2‐wayバンクコンフリクトdata[threadIdx.y][threadIdx.x]
data[0][threadIdx.x] data[1][threadIdx.x]
…
バンクコンフリクト
00
10
20
30
40
50
60
70
80
90100110120130140150
threadIdx.xthreadIdx.y
01
11
21
31
41
51
61
71
81
91101111121131141151
19 20 21 22 23 24 25 26 27 28 29 30 31 0 11 2 3
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0 1
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
バンクコンフリクトの低減(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論103
メモリパディングをした場合 読込のアクセス
書込のアクセス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][17] data[0][17]
data[1][17]
data[2][17]
data[3][17]
data[threadIdx.x][threadIdx.y]
//2‐wayバンクコンフリクトdata[threadIdx.y][threadIdx.x]
data[2][threadIdx.x] data[3][threadIdx.x]
…
バンクコンフリクト
00
10
20
30
40
50
60
70
80
90100110120130140150
threadIdx.xthreadIdx.y
01
11
21
31
41
51
61
71
81
91101111121131141151
__global__ void transpose_reducebankconflict(int *in, int *,out){__shared__ int data[Tx][Ty+1]; //バンクコンフリクトを回避int i = blockIdx.x*blockDim.x + threadIdx.x; //配列添字とスレッド番号のint j = blockIdx.y*blockDim.y + threadIdx.y; //対応を計算
//グローバルメモリから共有メモリへコピー(コアレスアクセス)data[threadIdx.x][threadIdx.y] = in[j*Nx + i];__syncthreads(); //ブロック内の全スレッドがコピーを完了するのを待機
i = blockIdx.y*blockDim.y + threadIdx.x;j = blockIdx.x*blockDim.x + threadIdx.y;
out[j*Ny + i] = data[threadIdx.y][threadIdx.x];
}
バンクコンフリクトを低減したカーネル
2015/05/07先端GPGPUシミュレーション工学特論104
transpose_reducebankconflict.cu
実行時間
2015/05/07先端GPGPUシミュレーション工学特論105
入力配列サイズ Nx×Ny = 4096×2048 スレッド数 Tx×Ty = 16×16
1.1倍高速化*
カーネル 実行時間 [ms]
copy(目標値) 0.802 / 1.57
transpose 1.34
transpose_shared 0.977transpose_reducebankconflict 0.926
*高速化率=基準となる実行時間/高速化したカーネルによる実行時間
2次元的な配列アクセスの優先方向
2015/05/07先端GPGPUシミュレーション工学特論106
CUDAで2次元的に並列化してアクセスする場合 threadIdx.xのスレッド群(threadIdx.yが一定)が連続なメモリアドレスにアクセス
C言語の多次元配列 2次元目の要素が連続になるようメモリに確保
transpose_shared.cuの共有メモリの使い方は不適切
in[],out[]
Nx
Ny
j
ithreadIdx.x
thre
adId
x.y
data[Tx][Ty]
Tx
Ty
j
ithreadIdx.x
thre
adId
x.y
2次元的な配列アクセスの優先方向
2015/05/07先端GPGPUシミュレーション工学特論107
共有メモリの宣言の変更 1次元目と2次元目を入れ替え
data[Tx][Ty]
data[Ty][Tx] 配列参照 1次元目の添字はthreadIdx.yを利用
2次元目の添字はthreadIdx.xを利用
threadIdx.xのスレッド群(threadIdx.yが一定)が連続なメモリアドレスにアクセス
data[Tx][Ty]
Tx
Ty
j
ithreadIdx.x
thre
adId
x.y
data[Ty][Tx]
Ty
Tx
i
jthreadIdx.y
thre
adId
x.x
__global__ void transpose_shared(int *in, int *,out){__shared__ int data[Tx][Ty]; //ブロック内のスレッドが使うサイズだけ宣言int i = blockIdx.x*blockDim.x + threadIdx.x; //配列添字とスレッド番号のint j = blockIdx.y*blockDim.y + threadIdx.y; //対応を計算
//グローバルメモリから共有メモリへコピー(コアレスアクセス)data[threadIdx.x][threadIdx.y] = in[j*Nx + i];__syncthreads(); //ブロック内の全スレッドがコピーを完了するのを待機
i = blockIdx.y*blockDim.y + threadIdx.x;j = blockIdx.x*blockDim.x + threadIdx.y;out[j*Ny + i] = data[threadIdx.y][threadIdx.x];
}
共有メモリを利用するカーネル
2015/05/07先端GPGPUシミュレーション工学特論108
transpose_shared.cu
__global__ void transpose_shared(int *in, int *,out){__shared__ int data[Ty][Tx]; //ブロック内のスレッドが使うサイズだけ宣言int i = blockIdx.x*blockDim.x + threadIdx.x; //配列添字とスレッド番号のint j = blockIdx.y*blockDim.y + threadIdx.y; //対応を計算
//グローバルメモリから共有メモリへコピー(コアレスアクセス)data[threadIdx.y][threadIdx.x] = in[j*Nx + i];__syncthreads(); //ブロック内の全スレッドがコピーを完了するのを待機
i = blockIdx.y*blockDim.y + threadIdx.x;j = blockIdx.x*blockDim.x + threadIdx.y;out[j*Ny + i] = data[threadIdx.x][threadIdx.y];
}
共有メモリの添字を交換したカーネル
2015/05/07先端GPGPUシミュレーション工学特論109
transpose_shared_indexchange.cu
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
64
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
128
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
0
256
252
248
244
240
236
232
228
224
220
216
208
204
200
196
192
配列添字を入れ替えた場合の共有メモリへのアクセス(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論110
配列添字を入れ替えた場合 読込のアクセス
書込のアクセス
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
//コンフリクトフリーdata[threadIdx.y][threadIdx.x]
・・・
data[0][threadIdx.x]
data[threadIdx.x][threadIdx.y]
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
256
252
248
244
240
236
232
228
224
220
216
208
204
200
196
配列添字を入れ替えた場合の共有メモリへのアクセス(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論111
配列添字を入れ替えた場合 読込のアクセス
書込のアクセス
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
//コンフリクトフリーdata[threadIdx.y][threadIdx.x]
・・・
data[threadIdx.x][threadIdx.y]
64 1280 192
data[1][threadIdx.x]
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
64
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
128
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
0
256
252
248
244
240
236
232
228
224
220
216
208
204
200
196
192
配列添字を入れ替えた場合の共有メモリへのアクセス(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論112
配列添字を入れ替えた場合 読込のアクセス
書込のアクセス
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
data[threadIdx.y][threadIdx.x]
・・・
data[threadIdx.x][0]
//16‐wayバンクコンフリクトdata[threadIdx.x][threadIdx.y]
バンクコンフリクト
124
120
116
112
108
104
100
96
92
88
84
80
76
72
68
188
184
180
176
172
164
160
160
156
152
148
144
140
136
132
60
56
52
48
44
40
36
32
28
24
20
16
12
8
4
256
252
248
244
240
236
232
228
224
220
216
208
204
200
196
配列添字を入れ替えた場合の共有メモリへのアクセス(Tesla世代)
2015/05/07先端GPGPUシミュレーション工学特論113
配列添字を入れ替えた場合 読込のアクセス
書込のアクセス
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
・・・
メモリアドレスに対するスレッドのアクセス要求
0
23
5
1
6
8
14
4
7
9
15
10111213
バンク番号
・・・
data[threadIdx.y][threadIdx.x]
//16‐wayバンクコンフリクトdata[threadIdx.x][threadIdx.y]
バンクコンフリクト
64 1280 192
data[threadIdx.x][1]
配列添字を入れ替えた場合の共有メモリへのアクセス(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論114
配列添字を入れ替えた場合 読込のアクセス
書込のアクセス
//コンフリクトフリーdata[threadIdx.y][threadIdx.x]
data[threadIdx.x][threadIdx.y]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16] data[0][16]
data[1][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15data[2][16]
data[3][16]
data[0][threadIdx.x] data[1][threadIdx.x]
00
10
20
30
40
50
60
70
80
90100110120130140150
01
11
21
31
41
51
61
71
81
91101111121131141151
threadIdx.xthreadIdx.y
配列添字を入れ替えた場合の共有メモリへのアクセス(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論115
配列添字を入れ替えた場合 読込のアクセス
書込のアクセス
//コンフリクトフリーdata[threadIdx.y][threadIdx.x]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16] data[0][16]
data[1][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15data[2][16]
data[3][16]
data[2][threadIdx.x] data[3][threadIdx.x]
data[threadIdx.x][threadIdx.y]
00
10
20
30
40
50
60
70
80
90100110120130140150
01
11
21
31
41
51
61
71
81
91101111121131141151
threadIdx.xthreadIdx.y
配列添字を入れ替えた場合の共有メモリへのアクセス(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論116
配列添字を入れ替えた場合 読込のアクセス
書込のアクセス
data[threadIdx.y][threadIdx.x]
//8‐wayバンクコンフリクトdata[threadIdx.x][threadIdx.y]
…
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16] data[0][16]
data[1][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15data[2][16]
data[3][16] …
data[threadIdx.x][0] data[threadIdx.x][1]
バンクコンフリクト
バンクコンフリクト
00
10
20
30
40
50
60
70
80
90100110120130140150
01
11
21
31
41
51
61
71
81
91101111121131141151
threadIdx.xthreadIdx.y
配列添字を入れ替えた場合の共有メモリへのアクセス(Fermi世代)
2015/05/07先端GPGPUシミュレーション工学特論117
配列添字を入れ替えた場合 読込のアクセス
書込のアクセス
data[threadIdx.y][threadIdx.x]
…
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
data[16][16] data[0][16]
data[1][16]
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15data[2][16]
data[3][16]
data[threadIdx.x][2] data[threadIdx.x][3]
バンクコンフリクト
バンクコンフリクト
//8‐wayバンクコンフリクトdata[threadIdx.x][threadIdx.y]
00
10
20
30
40
50
60
70
80
90100110120130140150
01
11
21
31
41
51
61
71
81
91101111121131141151
threadIdx.xthreadIdx.y
実行時間
2015/05/07先端GPGPUシミュレーション工学特論118
入力配列サイズ Nx×Ny = 4096×2048 スレッド数 Tx×Ty = 16×16
カーネル 実行時間 [ms]
copy(目標値) 0.802 / 1.57
transpose 1.34
transpose_shared 0.977transpose_reducebankconflict 0.926transpose_shared_indexchange 0.939
有意に高速化
__global__ void transpose_reducebankconflict(int *in, int *,out){__shared__ int data[Ty][Tx+1]; //バンクコンフリクトを回避int i = blockIdx.x*blockDim.x + threadIdx.x; //配列添字とスレッド番号のint j = blockIdx.y*blockDim.y + threadIdx.y; //対応を計算
//グローバルメモリから共有メモリへコピー(コアレスアクセス)data[threadIdx.y][threadIdx.x] = in[j*Nx + i];__syncthreads(); //ブロック内の全スレッドがコピーを完了するのを待機
i = blockIdx.y*blockDim.y + threadIdx.x;j = blockIdx.x*blockDim.x + threadIdx.y;
out[j*Ny + i] = data[threadIdx.x][threadIdx.y];
}
共有メモリの添字を交換,パディングしたカーネル
2015/05/07先端GPGPUシミュレーション工学特論119
transpose_reducebankconflict_indexchange.cu
実行時間
2015/05/07先端GPGPUシミュレーション工学特論120
入力配列サイズ Nx×Ny = 4096×2048 スレッド数 Tx×Ty = 16×16
カーネル 実行時間 [ms]
copy(目標値) 0.802 / 1.57
transpose 1.34
transpose_shared 0.977transpose_reducebankconflict 0.926transpose_shared_indexchange 0.939transpose_reducebankconflict
_indexchange 0.926
ほぼ同じ
付録 書込と読込のアクセスの影響
2015/05/07先端GPGPUシミュレーション工学特論121
スレッド数(Tx×Ty=16×16)で配列サイズを変更
読込と書込でコアレス・ストライドアクセスが変化 書込がコアレスアクセスになる方が高速になる傾向
GPUでは書込をキャッシュできないことが理由と思われる
カーネル実行時間 [ms]
4096×2048 2048×1024 1024×512
copy(目標値)(rw:コアレス/rw:ストライド)
0.802/1.57 0.202/0.389 0.055/0.103
transpose(r:コアレス,w:ストライド)
1.39 0.348 0.089
simple_transpose(r:ストライド,w:コアレス)
1.13 0.289 0.077