web-dev-qa-db-ja.com

シェーダーでの分岐の効率

この質問は多少根拠がないように見えるかもしれませんが、誰かがこのトピックについて理論的/実用的な経験を持っている場合、それを共有すると素晴らしいでしょう。

多くのテクスチャルックアップを使用する私の古いシェーダーの1つを最適化しようとしています。

3つの可能なマッピング平面のそれぞれと、ユーザーの近くにあるいくつかの面について、拡散反射、法線、鏡面反射光マップも適用する必要があります(parallax occlusion mappingのような)多くのテクスチャルックアップをもたらすマッピングテクニック。

プロファイリングは、テクスチャルックアップがシェーダーのボトルネックであることを示し、私はそれらのいくつかを削除してもかまいません。 入力パラメータの一部の場合テクスチャルックアップの一部が不要になることをすでに知っており、自明解決策は(pseudocode)のようなことを行います:

if (part_actually_needed) {
   perform lookups;
   perform other steps specific for THIS PART;
}

// All other parts.

さて、ここに質問があります。

私は正確には覚えていません(そのため、質問がngroundedである可能性があると述べました)が、最近読んだいくつかの論文で(残念ながら、名前を思い出せません) 次のようなものが述べられました:

提示された手法のパフォーマンスは、HARDWARE-BASED CONDITIONAL BRANCHINGが実装されています。

この種類のステートメントを覚えていたのは、多数のシェーダーのリファクタリングを開始して実装する直前でしたifベースの最適化I話していた。

だから-それを始める直前-シェーダーの分岐の効率について誰かが何か知っていますか?分岐によってシェーダーのパフォーマンスが大幅に低下するのはなぜですか?

そして、ifベースの分岐で実際のパフォーマンスを悪化させることさえ可能ですか?


あなたは言うかもしれません-試してみてください。はい、ここで誰も私を助けてくれないなら私はそうします:)

しかし、それでも、ifのケースで新しいGPUに効果があるのは、少し古いGPUにとって悪夢かもしれません。 そして、あなたが多くの異なるGPUを持っているのでない限り、その種の問題を予測することは非常に困難です(それは私のケースではありません)

そのため、誰かがそのことについて何かを知っているか、これらの種類のシェーダーのベンチマーク経験がある場合は、私はあなたの助けに本当に感謝します。


実際に機能している残りの脳細胞のほとんどは、GPUでの分岐は、CPUの分岐(通常、分岐予測の非常に効率的な方法であり、キャッシュミスを排除する)ほど効果的ではない可能性があることを伝えています。 GPUに実装するのは難しい/不可能かもしれません)。

残念ながら、このステートメントに実際の状況と共通点があるかどうかはわかりません...

39
Yippie-Ki-Yay

残念ながら、ここでの本当の答えは、ターゲットハードウェアで特定のケースのパフォーマンスアナライザーを使用して実際的なテストを行うことだと思います。特に、プロジェクトの最適化段階にいるように思えます。これは、ハードウェアが頻繁に変更されるという事実と特定のシェーダーの性質を考慮する唯一の方法です。

CPUでは、誤って予測された分岐が発生すると、パイプラインのフラッシュが発生し、CPUパイプラインが非常に深いため、実質的に20サイクル以上のオーダーで何かが失われます。 GPUに関しては少し異なります。パイプラインははるかに浅い可能性がありますが、分岐予測はなく、すべてのシェーダーコードが高速メモリに格納されますが、それは本当の違いではありません。

NVidiaとATIは比較的緊密に連携しているため、進行中のすべての詳細を正確に知ることは困難ですが、GPUは大規模な並列実行用に作成されていることが重要です。非同期シェーダーコアは多数ありますが、各コアは複数のスレッドを実行するように設計されています。私の理解では、各コアは任意のサイクルですべてのスレッドで同じ命令を実行することを期待しています(nVidiaはこのスレッドのコレクションを「ワープ」と呼びます)。

この場合、スレッドは頂点、ジオメトリエレメント、またはピクセル/フラグメントを表し、ワープはそれらの約32のコレクションです。ピクセルの場合、それらは画面上で互いに近いピクセルである可能性があります。問題は、1つのワープ内で、条件付きジャンプで異なるスレッドが異なる決定を行うと、ワープが発散し、すべてのスレッドで同じ命令を実行しなくなったことです。ハードウェアはこれを処理できますが、その方法が(少なくとも、私には)完全に明確ではありません。また、連続する世代のカードごとに少し異なる方法で処理される可能性もあります。最新の最も一般的なCUDA /コンピュートシェーダーフレンドリーなnVidiaが最適な実装である可能性があります。古いカードでは、実装が不十分な場合があります。最悪のケースは、if/elseステートメントの両側を実行する多くのスレッドを見つける場合です。

シェーダーの優れたトリックの1つは、この超並列パラダイムを活用する方法を学ぶことです。これは、追加のパス、一時的なオフスクリーンバッファー、ステンシルバッファーを使用して、シェーダーからCPUにロジックをプッシュすることを意味する場合があります。最適化によってさらに多くのサイクルが消費されるように見えることがありますが、実際には隠れたオーバーヘッドが削減されている可能性があります。

また、DirectXシェーダーのifステートメントを[branch]または[flatten]として明示的にマークできることに注意してください。フラット化スタイルは正しい結果を提供しますが、常にすべての命令を実行します。明示的に選択しない場合は、コンパイラーが自動的に選択し、[flatten]を選択する場合がありますが、これはサンプルには適していません。

覚えておくべきことの1つは、最初のテクスチャルックアップを飛び越えると、ハードウェアのテクスチャ座標微分演算が混乱することです。コンパイラエラーが発生するので、そうしないことをお勧めします。そうしないと、より優れたテクスチャリングサポートを見逃してしまう可能性があります。

32
David Jewsbury

条件が均一(つまり、パス全体で一定)の場合、フレームワークは本質的に2つのバージョンのシェーダーをコンパイルし(分岐が行われるかどうかにかかわらず)、入力に基づいてパス全体に対してこれらの1つを選択するため、分岐は基本的に無料です。変数。この場合、必ずifステートメントを使用してくださいwillシェーダーを高速化します。

条件が頂点/ピクセルごとに異なる場合は、実際にパフォーマンスが低下する可能性があり、古いシェーダーモデルは動的分岐もサポートしていません。

32
casablanca

多くの場合、両方のブランチは、補間器としての条件によって計算および混合できます。このアプローチは、ブランチよりもはるかに速く機能します。 CPUでも使用できます。例えば:

...

vec3 c = vec3(1.0, 0.0, 0.0); if (a == b) c = vec3(0.0, 1.0, 0.0);

置き換えることができます:

vec3 c = mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), (a == b));

...

26
Plushechnik

Kindle Fireでの実際のパフォーマンスベンチマークは次のとおりです。

フラグメントシェーダーで...

これは20fpsで実行されます:

lowp vec4 a = vec4(0.0, 0.0, 0.0, 0.0);
if (a.r == 0.0)
    gl_FragColor = texture2D ( texture1, TextureCoordOut );   

これは60fpsで実行されます。

gl_FragColor = texture2D ( texture1, TextureCoordOut );   
10
Shaun Neal

Ifベースの最適化についてはわかりませんが、必要と思われるテクスチャルックアップのすべての順列を作成し、それぞれ独自のシェーダーを作成して、適切な状況に応じて適切なシェーダーを使用するだけではどうでしょうか(特定のモデルまたはモデルの一部が必要なテクスチャを検索するために必要です)。 Bully for Xbox 360でこのようなことをしたと思います。

8
Jim Buck