GPUはなぜ速いのか — 実行モデルと並列化の限界
CPUとGPUは「速い」の定義が違います。トランジスタ予算の使い道、32スレッドを束ねるSIMTとワープ、分岐発散で性能が落ちる理由、占有率とレジスタ圧。最後はAmdahlの法則で「どこまで速くなるか」を着手前に見積もり、プロファイラのどの指標を見てCPU律速に気づくかまで降ります。
比喩: 名人ひとりか、平凡な百人か
同じ店を2通りに作れます。ひとつは、動線を完璧に頭に入れた名人をひとり置く店。次に何が要るかを先読みし、手を止める時間を極限まで削ります。もうひとつは、平凡な料理人を百人並べる店。ひとりひとりは遅く、誰かは必ずオーブンの前で突っ立っている。ところが百人いれば、誰かが待つあいだに別の誰かが動けます。待ち時間は一切消えていないのに、店全体が出す皿の数では後者が勝つ。
CPUとGPUの違いはこれです。CPUは1つの仕事を短い時間で終わらせる機械——レイテンシ最適化。GPUは単位時間あたりの仕事量を最大化する機械——スループット最適化。「速い」の定義が違うので、片方の物差しでもう片方を測ると必ず誤解します。
トランジスタ予算の使い道
チップに載るトランジスタは有限で、設計とはその予算をどこへ配るかという意思決定です。
CPUは大半を、1本の命令列を止めずに流すことへ使います。分岐予測器、投機実行、リオーダバッファ、大きなキャッシュ——これらは1つも演算をしません。すべて「1つのスレッドが待たされないように」するための投資です。
GPUは逆張りをしました。予測も投機もほとんどせず、その面積を演算器とレジスタファイルに回す。GPUの演算単位(NVIDIAならSM、ストリーミング・マルチプロセッサ)のレジスタファイルは数百KB規模で、同じチップのL1キャッシュより大きいことも珍しくありません。容量が欲しいのではなく、大量のスレッドの状態を退避も復帰もせずに常駐させるためです。おかげで切り替えはほぼ無料になり、「メモリ待ちが起きたら、待つあいだ別のスレッドを走らせる」が現実的な戦略になります。
メモリの壁のリトルの法則を思い出してください。帯域を埋めるには 件のアクセスを常に空中へ浮かせる必要がありました。GPUの数万スレッドは、その そのものです。 キャッシュでレイテンシを縮めるのではなく、並列度で覆い隠す。ここがGPU設計の芯です。
先に釘: 並列化が買えるのは定数倍だけ
ただし並列化で買えるのは定数倍です。 台使って 倍が上限で、計算量のオーダーは1段も下がりません。 は千台あっても のまま、 を10倍にすれば百倍になって返ってきます。
順序は動きません。まずオーダーを下げ、次にデータの動かし方(行列積のコスト)、それから並列化。後半のAmdahlの法則は、その最後の一手が何倍で頭打ちになるかを着手前に教えてくれる道具です。
SIMT — スレッドに見えて、動くのは束
大量のスレッドを本当に独立に走らせると、制御回路の節約になりません。命令を取って解読する回路が、スレッドの数だけ要るからです。
そこでGPUは妥協します。連続する32スレッドを1つの束にまとめ、束の全員へ同じ命令を流す。 NVIDIAではこの束をワープ(warp)、AMDではウェーブフロント(wavefront)と呼びます(幅は世代により32または64)。フェッチとデコードは束につき1回、演算器は32レーンぶん並び、同じ命令をそれぞれ別のデータに当てます。
これが SIMT(Single Instruction, Multiple Threads)です。プログラマにはスレッド単位の普通のコードを書かせながら、ハードは幅32のSIMDとして動く。書きやすさと効率のあいだに置かれた妥協であり、落とし穴の多くはこの継ぎ目から噴き出します。代表が、「束の全員に同じ命令」という前提が崩れる瞬間です。
分岐発散 — 束の中で意見が割れたとき
ワープの32レーンが if (x > 0) に差しかかり、17レーンが真、15レーンが偽だったとします。ハードは1つの命令しか流せないので、両方は同時に走りません。まず真の側を流し、偽だった15レーンはマスクで無効化して結果を捨てる。次に偽の側を流し、今度は17レーンぶんを捨てる。
コメント
コメントにはログインが必要です