訳注:拙訳は抜粋や意訳を多く含みます。原著を必ず確認してください。
[Pettineo 2018Pettineo, M. 2018. Breaking Down Barriers - Part 3: Multiple Command Processors. The Danger Zone. https://therealmjp.github.io/posts/breaking-down-barriers-part-3-multiple-command-processors/.]
Flushing Performance Down The Drain#
より良いアイデアをもたらすため、より現実に則した例に切り替えよう。GPUでグラフィクス処理を行う実際のエンジンでは、それぞれが前の段に依存する描画またはディスパッチのチェインを持つのが非常に一般的である。
(画像)
これを同じくらいの時間かかる大きなディスパッチとオーバーラップしたいとしよう。ステップひとつとオーバーラップするのは非常に単純である。しかし、そのGPUが、前に起動したシェーダが実行を終了するまで待機する必要があるコマンドに関してバリアを実装する我々の架空のGPUと似たセットアップを持つ場合、理想的ではないかもしれない。その場合、長いディスパッチの後の最初のバリアはオーバーラップのない長い期間を生じさせるかもしれないだろう。
(画像)
これはないよりましだが、素晴らしいものではない。関連するキャッシュのフラッシュやその他の行動はシェーダユニットが完全にアイドル状態となる期間であるので、我々は長いディスパッチをバリアのひとつに完全にオーバーラップさせたい。我々はこれを分割バリアで達成できるが、SIGNAL_POST_SHADER命令がすべての以前にキューへ追加されたスレッドが完了した後にシグナルするよう制限される場合、一度しかこれを行うことができないだろう。
(画像)
Two Command Processors Are Better Than One#
全く新しいMJP-4000を設計するとき、ハードウェアエンジニアは複数の独立したコマンドストリームを扱う最も単純な方法は思い切ってGPUのフロントエンドをコピー&ペーストすることであると決定した。
(画像)
2つの別個のスレッドキューにアタッチされた2つのコマンドプロセッサがある。シェーダコア数はMJP-3000と同じであり、最大スループットの理論値は増えていない。しかし、2つのコマンドプロセッサを用いることで、シェーダコアのアイドル時間を減らし、実行するジョブの全体的なスループットを改善する。
図を見ると、“一体どうやって2つのフロントエンドがシェーダコアを共有するのか?“と(当然)不思議に思うかもしれない。実際のGPUでは、これを行ういくつかの取り得る方法がある。しかし今は、二重のスレッドキューがコアを共有するのに非常に単純なスキームを用いると仮定しよう。
- 1つのキューだけが待機中のスレッドを持ち、いずれのシェーダコアも空ある場合、そのキューは処理でこれらのコアを埋める
- 両方のキューが処理を持ち、空のコアがある場合、これらのコアは平等に分割され、両方のスレッドキューからスレッドを割り当てられる(奇数の場合、上のキューが追加のコアを得る)
- スレッドは、ひとたびシェーダコアに割り当てられれば、常に完了するまで実行する。これは待機中のスレッドが割り込みできないことを意味する
では、このすべてが実際にどう動くかを見てみよう。
(画像)
詳細は少し複雑だが、言うなれば、2つのコマンドバッファを一度に実行することによって、一度にひとつを実行した場合と比べてより高い総使用率を得た。しかし、この高使用率は個々のチェインごとのより高いレイテンシーのコストに原因があった。別の言い方をすれば、GPUの全体のスループットを改善した。
深度のみのレンダリングでは、シェーダコアは頂点シェーダで頂点を変換するために使われるが、ピクセルシェーダは使わない。頂点数はピクセル数と比べてかなり少ない傾向にあるので、固定機能によってボトルネックになってしまうことはこれらのパスでは容易に起こる。そうなれば、その頂点シェーダはシェーダコアの一部しか使わないので、他のコマンドストリームが活用できるような多くのアイドル状態のコアが残ることになる。
このマルチキュー構成をCPU世界で言うなれば、Simultneous Multithreading、縮めてSMT(Intelで言う所のHyper-Threading)に当たる。
Syncing Across Queues#
これまで、チェイン1とチェイン2は完全に独立であると考えていた。しかし、単一のアプリケーションでは単一点から開始する2つの異なるチェインを持つことがかなり一般的であり、この最終結果はもうひとつの単一のディスパッチによって必要とされる。より具体的な例として、メインパスをレンダターゲットにレンダリングした後に、ブルームとDOFの2つのポストプロセッシング処理を行い、両方の結果をスクリーン上で合成するとしよう。これらの処理の依存性グラフを作るとすれば、以下のような感じになるだろう。
(画像)
グラフから、ブルームとDOFのチェインは独立であるのは明らかだが、チェインの開始と終了で同期する必要がある。MJP-4000の二重フロントエンドにサブミットされる別個のコマンドバッファとして実行されるとした場合、正しい結果を保証するためのある種のキュー間同期を必要とするだろう。
(画像)
MJP-4000ではSIGNAL_POST_SHADERとWAIT_SIGNAL命令を再利用することによる単純なアプローチを取る。