拙訳
CPUとGPUのトレンド(CPU and GPU Trends)
- FLOPS --- 秒あたり浮動小数点演算数
- GFLOPS --- 10億(10^9)FLOPS
- TFLOPS --- 1000GFLOPS
あなたの車に7.5TFLOPS!(7.5 TFLOPS in your car!)
- NVIDIA DRIVE PX 2
- 最大で
- 2つのTegra SoC(2.5TFLOPS)
- 2つのPascal GPU(5TFLOPS)
- 最大で
…あたりのFLOP(FLOP per…)
- 2003年以降のシングルコアのパフォーマンスの低下
- 電力や熱の限界
- GPUは以下のより高いFLOPを届ける
- ワットあたり
- 大きさあたり
- 費用あたり
CPUレビュー(CPU Review)
- CPUダイの中の主要な構成要素は何か?
- デスクトップアプリケーション
- 軽めのスレッド化
- 大量の分岐
- 大量のメモリアクセス
分岐予測(Branch Prediction)
- 近年の予測器は90%以上の正確さ
- パフォーマンスとエネルギー効率の向上
- 面積の増大
- フェッチステージのレイテンシーの増加の可能性
メモリ階層(Memory Hierarchy)
- メモリ: 大きくなるほど、遅くなる
- 大まかな数値:
| レイテンシー | 帯域幅 | サイズ | |
|---|---|---|---|
| SRAM(L1、L2、L3) | 1-2ns | 200GBps | 1-20MB |
| DRAM(メモリ) | 70ns | 20GBps | 1-20GB |
| フラッシュ/SSD(ディスク) | 70-90us | 200-500MBps | 100-1000GB |
| HDD(ディスク) | 10ms | 1-150MBps | 500-3000GB |
キャッシング(Caching)
- 必要なデータを近くに置き続ける
- 活用する:
- 時間的な局所性
- たった今使われたチャンクはすぐに再び使われる可能性が高い
- 空間的な局所性
- 次に使うチャンクは前のものの近くにある可能性が高い
- 時間的な局所性
キャッシュ階層(Cache Hierarchy)
- ハードウェア管理
- L1 命令/データキャッシュ
- L2 ユニファイドキャッシュ
- L3 ユニファイドキャッシュ
- ソフトウェア管理
- メインメモリ
- ディスク
IPCの改善(improvig IPC)
- IPC(サイクルあたりの命令数)は1命令/クロックでボトルネックになる
- スーパースカラ --- パイプラインの幅を広げる
スケジューリング(Scheduling)
- 以下の命令を考える:
xor r1, r2 -> r3
add r3, r4 -> r4
sub r5, r2 -> r3
addi r3, 1 -> r1addはxorに依存している(Read-After-Write、RAW)subとaddiは依存している(RAW)xorとsubは依存していない(Write-After-Write、WAW)
レジスタリネーミング(Register Renaming)
- 代わりにこれならどうだろう:
xor p1, p2 -> p6
add p6, p4 -> p7
sub p5, p2 -> p8
addi p8, 1 -> p9xorとsubが並列に実行できる
アウトオブオーダー実行(Out-of-Order Execution)
- スループットを最大化するための命令の入れ替え
- フェッチ→デコード→リネーム→ディスパッチ→発行→レジスタ読み込み→実行→記憶→リードバック→コミット
- リオーダーバッファ(ROB)
- 実行中の命令の状態を追跡し続ける
- 物理レジスタファイル(PRF)
- キュー/スケジューラを発行する
- 実行する次の命令を選ぶ
CPUでの並列化(Parallelism in the CPU)
- 命令レベル(ILP)の抽出をカバーする
- スーパースカラ
- アウトオブオーダー
- データレベル並列化(DLP)
- ベクタ
- スレッドレベル並列化(TLP)
- 同時マルチスレッディング(SMT)
- マルチコア
ベクタの動機づけ(Vectors Motivation)
for (int i = 0; i < N; i++)
A[i] = B[i] + C[i];CPUデータレベル並列化(CPU Data-Level Parallelism)
- Single Instruction Multiple Data (SIMD)
- 実行単位(ALU)をかなり広くしよう
- レジスタもかなり広くしよう
for (int i = 0; i < N; i += 4) {
// 並列化
A[i] = B[i] + C[i];
A[i+1] = B[i+1] + C[i+1];
A[i+2] = B[i+2] + C[i+2];
A[i+3] = B[i+3] + C[i+3];
}x86でのベクタ処理(Vector Operations in x86)
- SSE2
- 4つ幅のパック済みfloat及びパック済みint命令
- Intel Pentium 4以降
- AMD Athlon 64以降
- AVX
- 8つ幅のパック済みfloat及びパック済みint命令
- Intel Sandy Bridge
- AMD Bulldozer
スレッドレベル並列化(Thread-Level Parallelism)
- スレッドの構成物
- 命令ストリーム
- プライベートなPC1、レジスタ、スタック
- 共有されたグローバル領域、ヒープ
- プログラマーによって生成、破棄される
- プログラマーかOSによってスケジュールされる
同時マルチスレッディング(Simultaneous Multithreading)
- 命令を複数スレッドから発行できる。
- ROBや他のバッファの分割が必要
- ✅ 最小のハードウェア重複
- ✅ アウトオブオーダーに対する更なるスケジューリング自由度
- ❌ キャッシュや実行リソース競合がシングルスレッドのパフォーマンスを低下させる可能性がある
マルチコア(Multicore)
- 完全なパイプラインを複製する
- Sandy Bridge-E: 6コア
- ✅ 完全なコア、最終レベルのキャッシュ以外でリソース共有がない
- ✅ ムーアの法則の利点を得るより簡単な方法
- ❌ 利用率
CPUの結論(CPU Conclusions)
- CPUはシーケンシャルプログラミングに最適化されている
- パイプライン、分岐予測、スーパースカラ、アウトオブオーダー
- 高クロック速度や高利用率で実行時間を減らす
- 低速なメモリは変わらない問題である
- 並列化
- Sandy Bridge-Eは6-12のアクティブスレッドで素晴らしい
- 32Kのスレッドだとどうだろう?
グラフィクスの仕事量(Graphics Workloads)
- トライアングル/頂点やピクセル/フラグメント
90年代初期 --- GPU以前(Early 90s - Pre GPU)
- Wolfenstein 3D, 1992 // Doom I, 1993
- インタラクティブソフトウェアレンダリング(まだGPUではない)
- 注: SGIはインタラクティブレンダリングスーパーコンピューターを作っていたが、これはPCでのインタラクティブ3Dグラフィクスの始まりであった
なぜGPUか?(Why GPUs?)
- グラフィクスの仕事は呆れるほど並列的である
- データ並列
- パイプライン並列
- CPUとGPUは並列に実行する
- ハードウェア: テクスチャフィルタリング、ラスタライゼーション、など
データ並列(Data Parallel)
- グラフィクスの先へ
- クロスシミュレーション
- パーティクルシステム
- 行列の乗算
ディフューズ反射率シェーダ(A diffuse reflectance shader)
- シェーダプログラミングモデル:
- フラグメントは独立して処理されるが、そこに明示的な並列プログラミングは存在しない
シェーダをコンパイルする(Compile shader)
1つのシェードされていないフラグメント入力レコード
↓
(ソースコード)→(アセンブリコード)
↓
1つのシェードされたフラグメント出力レコード
シェーダを実行する(Execute shader)
フェッチ/デコード
ALU(実行)
実行コンテキスト
CPU: マルチコア時代以前(CPU: pre multi-core era)
チップのトランジスタの大部分は単一命令ストリームが高速に動作するのに役立つ処理を行う
より多いトランジスタ = より大きいキャッシュ、より賢いアウトオブオーダーロジック、より賢い分岐予測器、など
(一緒: より多いトランジスタ→より小さなトランジスタ→より高いクロック周波数)
CPU: マルチコア時代(CPU: multi-core era)
アイデア #1:
増加するトランジスタ数を用いてプロセッサにコアを追加する
単一命令ストリームを高速化するプロセッサロジックの複雑化を進めるためにトランジスタを用いるのではなく
2コア(並列に2フラグメント)(Two cores (two fragments in parallel))
4コア(並列に4フラグメント)(Four cores (four fragments in parallel))
16コア(並列に16フラグメント)(Sixteen cores (sixteen fragments in parallel))
命令ストリームの共有(Instruction stream sharing)
しかし…命令ストリームは共有できるはず!
想起: 単純なプロセッシングコア(Recall: simple processing core)
ALUを追加する(Add ALUs)
アイデア #2:
多くのALU全体で命令ストリームを管理するコスト/複雑さを償却する
SIMD処理
シェーダの修正(Modifying the shader)
(ベクトル化したアセンブリコード)
並列に128フラグメント(128 fragments in parallel)
16コア = 128ALU、16の同時命令ストリーム
並列に128の(頂点/フラグメント/プリミティブ/OpenCLワークアイテム)(128 [vertices/fragments primitives OpenCL work items] in parallel)
だけど、分岐は?(But what about brahches?)
すべてのALU有用な処理をしているわけではない!
ワーストケース: ピークパフォーマンスの1/8
分類(Classification)
- SIMD処理はSIMD命令を暗示していない
- 選択肢1: 明示的なベクタ命令
- x86 SSE、AVX、Intel Larrabee
- 選択肢2: スカラ命令、暗黙的なハードウェアベクトル化
- ハードウェアはALu間命令ストリーム共有を定める(共有の量はソフトウェアからは見えない)
- NVIDIA GeForce(“SIMT” warps)、ATI Radionアーキテクチャ(“wavefronts”)
- 選択肢1: 明示的なベクタ命令
実践では: 16から64のフラグメントがひとつの命令ストリームを共有する
ストール!(Stalls!)
ストールは、前の処理への依存性により、コアが次の命令を実行できないときに発生する
テクスチャアクセスのレイテンシー = 数百から数千サイクル
ストールを回避するのに役立つ過度なキャッシュやロジックを取り除いてきた
キャッシュはストールの長さを減らす(レイテンシーを減らす)(Caches reduce length of stalls (reduce latency))
プロセッサはデータがキャッシュの中にあるときに効率的に動作する
(キャッシュはメモリアクセスのレイテンシーを減らし、CPUへの高い帯域幅をももたらす)
ただし、我々には大量の独立したフラグメントがある
アイデア #3:
高レイテンシーな処理によって引き起こされるストールを回避するため、シングルコアで多くのフラグメントの処理をインターリーブする
シェーダストールの隠蔽(Hiding shader stalls)
多くのグループのスループットを増加させるため、ひとつのグループの実行時間を増加させる
コンテキストの格納(Storing contexts)
18つの小さなコンテキスト(最大限のレイテンシー隠蔽)(Eighteen small contexts (maximal latency hiding))
12つの中くらいのコンテキスト(Twelve medium contexts)
4つの大きなコンテキスト(低いレイテンシー隠蔽能力)(Four large contexts (low latency hiding ability))
例のチップ(Example chip)
16個のコア
コアあたり8個のmul-add ALU(総計128)
16個の同時命令ストリーム
64個の並行(だが、インターリーブした)命令ストリーム
512個の並行フラグメント
256GFLOPS (@ 1GHz)
CPU対GPUのメモリ階層(CPU vs. GPU memory hierarchies)
CPU:
大きいキャッシュ、少ないスレッド、控えめのメモリ帯域幅
キャッシュとプリフェッチに主に頼っている
GPU:
小さなキャッシュ、多くのスレッド、巨大なメモリ帯域幅
マルチスリディングに主に頼っている
思考実験(Thought experiment)
タスク: 2つのベクトルAとBの要素ごとの乗算
ベクトルは数百万要素を含むと仮定する
- 入力
A[i]をロードする - 入力
B[i]をロードする A[i] * B[i]を計算する- 結果を
C[i]に格納する
すべてのMULごとに3つのメモリ処理(12バイト)
NVIDIA GTX 380 GPUはクロックあたり480回のMULが可能(@ 1.4GHz)
機能ユニットをビジーにし続けるには、約7.5TB/secの帯域幅が必要になる(177GB/secしかない)
約2%の効率…だがCPUより8倍速い!
(25GB/secのメモリバスに接続された3GHzのCore i7クアッドコアCPUはこの計算で似たように低効率を示すだろう)
帯域幅は制限されている!(Bandwidth limited!)
プロセッサが高すぎる割合でデータを要求する場合、メモリシステムはそれを維持できない
レイテンシー隠蔽の量はこの助けにならない
帯域幅制限の克服はスループットで最適化されたシステムのアプリケーション開発者に対して一般的な問題である
GPUの中身は?(What’s in a GPU?)
GPUは(グラフィクスにかなり調整された)ヘテロジニアスチップマルチプロセッサである
まとめ: 3つの重要なアイデア(Summary: three key ideas)
- 並列に動作するために多くの”スリムダウンしたコア”を用いる
- コアをALUでいっぱいにする(フラグメントのグループ間で命令ストリームを共有することで)
- 選択肢1: 明示的なSIMDベクタ命令
- 選択肢2: ハードウェアによって管理された暗黙的な共有
- フラグメントの多くのグループの実行をインターリーブすることによってレイテンシーストールを回避する
- ひとつのグループがストールするとき、他のグループが動作する
Footnotes
-
訳注:プログラムカウンタ ↩