DirectX 10シェーダーのifステートメントを避けますか?


14

ステートメントの両方の部分が実行され、間違ったものがドロップされるため、シェーダーでステートメントを回避する必要があると聞いたことがあります(パフォーマンスに悪影響を及ぼします)。

DirectX 10ではまだ問題ですか?誰かが私に言った、その中で正しいブランチだけが実行されるだろう。

説明のために、私はコードを持っています:

float y1 = 5; float y2 = 6; float b1 = 2; float b2 = 3;

if(x>0.5){
    x = 10 * y1 + b1;
}else{
    x = 10 * y2 + b2;
}

より高速にする他の方法はありますか?

もしそうなら、どうやって?

両方のブランチは似ていますが、唯一の違いは「定数」の値です(y1, y2, b1, b2ピクセルシェーダーのすべてのピクセルで同じです)。


1
正直なところ、これは非常に時期尚早な最適化です。コードをベンチマークし、シェーダーがボトルネックであることが100%になるまで、変更しないでください。
pwny

回答:


17

マイクロ最適化シェーダーの多くのルールは、ベクトル拡張機能を備えた従来のCPUと同じです。ここにいくつかのヒントがあります:

  • 組み込みのテスト関数があります(testlerp/ mix
  • 2つのベクトルを追加すると、2つのフロートを追加するのと同じコストになります
  • スウィズルは無料です

最近のハードウェアでは、ブランチが以前よりも安くなっているのは事実ですが、可能であれば回避する方が良いでしょう。スウィズル関数とテスト関数を使用すると、テストなしでシェーダーを書き換えることができます。

/* y1, y2, b1, b2 */
float4 constants = float4(5, 6, 2, 3);

float2 tmp = 10 * constants.xy + constants.zw;
x = lerp(tmp[1], tmp[0], step(x, 0.5));

stepandの使用lerpは、2つの値から選択するための非常に一般的なイディオムです。


6

一般的には大丈夫です。シェーダーは頂点またはピクセルのグループで実行されます(異なるベンダーはこれらに対して異なる用語を持っているため、私はそれを避けています)。グループ内のすべての頂点またはピクセルが同じパスを取る場合、分岐コストは無視できます。

シェーダーコンパイラも信頼する必要があります。作成するHLSLコードは、コンパイルするバイトコードまたはアセンブリの直接的な表現と見なされるべきではなく、コンパイラーはそれを同等であるが分岐を回避するものに完全に自由に変換できます(たとえば、lerpは優先コンバージョン)。一方、ブランチの実行が実際により速いパスであるとコンパイラが判断した場合、コンパイラはそれをブランチにコンパイルします。生成されたアセンブリをPIXまたは同様のツールで表示すると、ここで非常に役立ちます。

最後に、古い知恵はまだここに保持されています-それをプロファイルし、それが実際にあなたにとってパフォーマンスの問題であるかどうかを判断し、それ以前にではなく、それに取り組みます。何かパフォーマンスの問題であると仮定し、その仮定に従って行動することは、後でより大きな問題の大きなリスクを負うだけです。


4

Robert Rouhaniが投稿したリンク/記事からの引用:

「条件コード(予測)は、古いアーキテクチャで真の分岐をエミュレートするために使用されます。これらのアーキテクチャにコンパイルされたif-thenステートメントは、すべてのフラグメントで成立および非成立の両方の分岐命令を評価する必要があります。分岐条件が評価され、条件コードが設定されます。分岐の各部分の命令は、結果をレジスタに書き込む前に条件コードの値をチェックする必要があるため、取得された分岐の命令のみが出力を書き込みます。ブランチ、およびブランチ条件の評価コスト。ブランチは、このようなアーキテクチャで控えめに使用する必要があります。NVIDIAGeForce FXシリーズGPUは、フラグメントプロセッサで条件コードブランチエミュレーションを使用します。

mh01が提案したように(「PIXまたは同様のツールで生成されたアセンブリを表示すると、ここで非常に役立つことがあります。」)、コンパイラツールを使用して出力を調べる必要があります。私の経験では、nVidiaのCgツール(Cgはクロスプラットフォーム機能のために今日でも広く使用されています)は、GPU gemの条件コード(予測)の段落で述べられている動作の完全な説明を提供しました。したがって、トリガー値に関係なく、両方のブランチはフラグメントごとに評価され、最後にのみ正しいブランチが出力レジストリに配置されました。それでも、計算時間は無駄になりました。当時、特にすべての理由で分岐がパフォーマンスに役立つと考えましたそのシェーダーのフラグメントは、正しい分岐を決定するために均一な値に依存していました-これは意図したとおりに行われませんでした。したがって、ここでの重要な注意事項(たとえば、ubershadersを避ける-分岐地獄の最大のソース)。


2

パフォーマンスの問題がまだない場合は、これで問題ありません。定数と比較するためのコストは依然として非常に安価です。GPUの分岐については、http//http.developer.nvidia.com/GPUGems2/gpugems2_chapter34.htmlをご覧ください。

とにかく、ifステートメントよりもはるかに悪く実行されるコードのスニペットがありますが(読み取りやメンテナンスがはるかに少なくなります)、それでもそれは取り除かれます。

int fx = floor(x);
int y = (fx * y2) + ((1- fx) * y1);
int b = (fx * b2) + ((1 -fx) * b1);

x = 10 * y + b;

xがrangeに制限されていると仮定していることに注意してください[0, 1]。x> = 2またはx <0の場合、これは機能しません。

切り取ったのは、xを0orに変換し1、間違ったものに0を掛け、もう1つに1を掛けることです。


元のテストはif(x<0.5)の値であるためfxround(x)またはになりfloor(x + 0.5)ます。
サムホセバー

1

分岐せずに条件を実行できる複数の命令があります。

vec4 when_eq(vec4 x, vec4 y) {
  return 1.0 - abs(sign(x - y));
}

vec4 when_neq(vec4 x, vec4 y) {
  return abs(sign(x - y));
}

vec4 when_gt(vec4 x, vec4 y) {
  return max(sign(x - y), 0.0);
}

vec4 when_lt(vec4 x, vec4 y) {
  return max(sign(y - x), 0.0);
}

vec4 when_ge(vec4 x, vec4 y) {
  return 1.0 - when_lt(x, y);
}

vec4 when_le(vec4 x, vec4 y) {
  return 1.0 - when_gt(x, y);
}

プラスいくつかの論理演算子。

vec4 and(vec4 a, vec4 b) {
  return a * b;
}

vec4 or(vec4 a, vec4 b) {
  return min(a + b, 1.0);
}

vec4 xor(vec4 a, vec4 b) {
  return (a + b) % 2.0;
}

vec4 not(vec4 a) {
  return 1.0 - a;
}

ソース:http : //theorangeduck.com/page/avoiding-shader-conditionals

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.