==はGLSLの分岐を引き起こしますか?


27

GLSLで分岐の原因とそうでないものを正確に把握しようとしています。

私はシェーダーでこれをたくさんやっています:

float(a==b)

条件分岐なしでifステートメントをシミュレートするために使用していますが、効果的ですか?現在、プログラムのどこにもif文はなく、ループもありません。

編集:明確にするために、私は私のコードでこのようなことをします:

float isTint = float((renderflags & GK_TINT) > uint(0)); // 1 if true, 0 if false
    float isNotTint = 1-isTint;//swaps with the other value
    float isDarken = float((renderflags & GK_DARKEN) > uint(0));
    float isNotDarken = 1-isDarken;
    float isAverage = float((renderflags & GK_AVERAGE) > uint(0));
    float isNotAverage = 1-isAverage;
    //it is none of those if:
    //* More than one of them is true
    //* All of them are false
    float isNoneofThose = isTint * isDarken * isAverage + isNotTint * isAverage * isDarken + isTint * isNotAverage * isDarken + isTint * isAverage * isNotDarken + isNotTint * isNotAverage * isNotDarken;
    float isNotNoneofThose = 1-isNoneofThose;

    //Calc finalcolor;
    finalcolor = (primary_color + secondary_color) * isTint * isNotNoneofThose + (primary_color - secondary_color) * isDarken * isNotNoneofThose + vec3((primary_color.x + secondary_color.x)/2.0,(primary_color.y + secondary_color.y)/2.0,(primary_color.z + secondary_color.z)/2.0) * isAverage * isNotNoneofThose + primary_color * isNoneofThose;

編集:私は分岐したくない理由を知っています。分岐とは何かを知っています。分岐について子供たちに教えてくれてうれしいですが、ブール演算子については自分自身を知りたいと思います(そしてビット単位の操作ですが、私はそれらがうまくいくと確信しています)

回答:


42

GLSLの分岐の原因は、GPUモデルとOpenGLドライバーのバージョンによって異なります。

ほとんどのGPUには、分岐コストのない「2つの値のいずれかを選択」操作の形式があるようです。

n = (a==b) ? x : y;

そして、時には次のようなものです:

if(a==b) { 
   n = x;
   m = y;
} else {
   n = y;
   m = x;
}

分岐ペナルティなしで、いくつかの選択値操作に削減されます。

一部のGPU /ドライバーでは、2つの値間の比較演算子に多少のペナルティーがありますが、ゼロとの比較ではより高速な操作が行われます。

実行する方が速い場合:

gl_FragColor.xyz = ((tmp1 - tmp2) != vec3(0.0)) ? E : tmp1;

(tmp1 != tmp2)直接比較するのではなく、これは非常にGPUとドライバに依存しているので、非常に特定のGPUと他のGPUをターゲットにしていない限り、比較操作を使用することをお勧めし、その最適化ジョブをOpenGLドライバに残しますよりシンプルで読みやすい方法でより高速になります。

「ブランチ」も常に悪いことではありません。たとえば、OpenPandoraで使用されるSGX530 GPUでは、このscale2xシェーダー(30ms):

    lowp vec3 E = texture2D(s_texture0, v_texCoord[0]).xyz;
    lowp vec3 D = texture2D(s_texture0, v_texCoord[1]).xyz;
    lowp vec3 F = texture2D(s_texture0, v_texCoord[2]).xyz;
    lowp vec3 H = texture2D(s_texture0, v_texCoord[3]).xyz;
    lowp vec3 B = texture2D(s_texture0, v_texCoord[4]).xyz;
    if ((D - F) * (H - B) == vec3(0.0)) {
            gl_FragColor.xyz = E;
    } else {
            lowp vec2 p = fract(pos);
            lowp vec3 tmp1 = p.x < 0.5 ? D : F;
            lowp vec3 tmp2 = p.y < 0.5 ? H : B;
            gl_FragColor.xyz = ((tmp1 - tmp2) != vec3(0.0)) ? E : tmp1;
    }

この同等のシェーダー(80ms)よりも劇的に高速になりました:

    lowp vec3 E = texture2D(s_texture0, v_texCoord[0]).xyz;
    lowp vec3 D = texture2D(s_texture0, v_texCoord[1]).xyz;
    lowp vec3 F = texture2D(s_texture0, v_texCoord[2]).xyz;
    lowp vec3 H = texture2D(s_texture0, v_texCoord[3]).xyz;
    lowp vec3 B = texture2D(s_texture0, v_texCoord[4]).xyz;
    lowp vec2 p = fract(pos);

    lowp vec3 tmp1 = p.x < 0.5 ? D : F;
    lowp vec3 tmp2 = p.y < 0.5 ? H : B;
    lowp vec3 tmp3 = D == F || H == B ? E : tmp1;
    gl_FragColor.xyz = tmp1 == tmp2 ? tmp3 : E;

特定のGLSLコンパイラまたは特定のGPUがどのように動作するかを、ベンチマークするまで事前に知ることはできません。


toポイントを追加するために(この部分を示す実際のタイミング番号とシェーダーコードがない場合でも)、現在、通常のテストハードウェアとして使用しています。

  • Intel HDグラフィックス3000
  • Intel HD 405グラフィックス
  • nVidia GTX 560M
  • nVidia GTX 960
  • AMD Radeon R7 260X
  • nVidia GTX 1050

テストするさまざまな一般的なGPUモデルの広い範囲として。

Windows、Linux独自、およびLinuxオープンソースOpenGLおよびOpenCLドライバーでそれぞれをテストします。

そして、GLSLシェーダー(上記のSGX530の例のように)または特定のGPU /ドライバーコンボのOpenCL操作をマイクロ最適化しようとするたびに、他の複数のGPU /ドライバーのパフォーマンスを等しく損なう ことになります。

したがって、高レベルの数学的な複雑さを明確に減らす(たとえば、5つの同一の除算を単一の逆数に変換し、代わりに5つの乗算する)ことと、テクスチャルックアップ/帯域幅を減らすこと以外は、時間の無駄です。

すべてのGPUは他のGPUとは異なります。

特定のGPUを備えたゲームコンソールで特に作業している場合、これは別の話になります。

この他の(小さなゲーム開発者にとっては重要ではありませんが、まだ注目に値する)側面は、コンピューターのGPUドライバーがいつかあなたのシェーダーを(特定のゲームが十分に普及した場合)その特定のGPU向けに最適化されたカスタムの書き換えられたシェーダーに静かに置き換える可能性があることです。それがすべてあなたのために働く。

彼らは、ベンチマークとして頻繁に使用される人気のあるゲームに対してこれを行います。

あるいは、プレイヤーが自分で簡単に編集できるようにシェーダーにアクセスできるようにすると、自分の利益のためにいくつかの余分なFPSを絞ることができます。

たとえば、Oblivion用のファン製のシェーダーとテクスチャパックがあり、ほとんどプレイできないハードウェアでフレームレートを劇的に向上させます。

最後に、シェーダーが十分に複雑になり、ゲームがほぼ完了し、さまざまなハードウェアでテストを開始すると、さまざまなバグが原因でシェーダーをさまざまなGPUで動作するように修正するだけで十分に忙しくなります。その程度に最適化する時間があります。


「または、プレイヤーが自分で簡単に編集できるようにシェーダーにアクセスできるようにした場合...」これについて言及したので、ウォールハックシェーダーなどへのアプローチはどうでしょうか?名誉制度、検証済み、報告...?最大/最小/スケーラブルなリアリズム、エクスプロイトなどのスタンスは、レビューやコラボレーションなどを促進するためにプレイヤーとモッダーを結び付けるため、ロビーは同じシェーダー/アセットに制限されているという考えが好きです。これがGaryのModの動作方法であることを思い出してください。
ジョンP

1
@JohnPセキュリティは、クライアントが危険にさらされていないことを前提とするものは何でも動作しません。もちろん、ユーザーがシェーダーを編集することを望まない場合、それらを公開することは意味がありませんが、それは実際にはセキュリティでそれほど役に立ちません。ウォールハックのようなものを検出するための戦略は、クライアントサイドの物事を低い最初の障壁として扱う必要があります。おそらく、この答えのように軽い改造を許可すると、プレーヤーにとって検出可能な不公平な利点につながらない場合、より大きな利点があるかもしれません。
キュービック

8
@JohnPプレイヤーがあまりにも壁越しに見えないようにしたい場合は、サーバーに壁の背後にあるものに関する情報を送信させないでください。
ポリグノーム

1
それだけです-何らかの理由でそれを好むプレイヤー間のウォールハッキングに反対ではありません。しかし、プレーヤーとして、私はいくつかのAAAタイトルを放棄しました。なぜなら、他の理由の中でも、彼らはお金/ XPなどで美的改造者の例を作ったからです。ハッカーは無傷で(支払いに不満を抱いている人たちから本当のお金を稼ぎました)、レポートとアピールシステムの人員不足と自動化を行い、生き続けるために世話をしたサーバーの数によってゲームが生き、死ぬことを確認しました。開発者とプレイヤーの両方として、より分散化されたアプローチがあることを期待していました。
ジョンP

いいえ、私はどこかでインラインを実行しません。私はただfloat(boolean statement)*(何か)
Geklmintendon't of Awesome

7

@Stephane Hockenhullの答えは、あなたが知っておくべきことをほとんど与えてくれます。それは完全にハードウェアに依存するでしょう。

しかし、どのようにいくつかの例を挙げましょう分岐がとき、それはハードウェアに依存することができが、なぜ分岐がすべてでさえ問題で、GPUは、舞台裏で何をするのかて起こります。

私は主にNvidiaに焦点を当てており、低レベルのCUDAプログラミングの経験があり、どのPTX(CUDA カーネルのIR SPIR-VなどのNvidiaのみ)が生成され、特定の変更を行うベンチマークを確認しています。

GPUアーキテクチャでの分岐がそれほど重要なのはなぜですか?

そもそも分岐するのが悪いのはなぜですか?GPUがそもそも分岐を回避しようとするのはなぜですか?GPUは通常、スレッドが同じ命令ポインターを共有するスキームを使用するためです。GPUはSIMDアーキテクチャに従います通常、その粒度は変化する可能性があります(つまり、Nvidiaでは32スレッド、AMDなどでは64スレッド)が、あるレベルでは、スレッドのグループが同じ命令ポインターを共有します。つまり、これらのスレッドは、同じ問題に取り組むために同じコード行を見る必要があるということです。同じコード行をどのように使用し、異なることを行うことができるかを尋ねることができますか?これらはレジスタで異なる値を使用しますが、それらのレジスタはグループ全体で同じコード行で引き続き使用されます。それが事実でなくなるとどうなりますか?(IEブランチ?)プログラムが本当にそれを回避できない場合、グループを分割します(32スレッドのNvidiaこのようなバンドルは、AMDおよび並列コンピューティングアカデミアの場合、Warpと呼ばれ、波面と呼ばれます)2つ以上の異なるグループに。

最終的に異なるコード行が2行しかない場合、作業スレッドは2つのグループに分割されます(ここでは、1つをワープと呼びます)。ワープサイズが32のNvidiaアーキテクチャを想定してみましょう。これらのスレッドの半分が発散すると、32個のアクティブスレッドが2つのワープを占有することになり、計算処理からプットエンドまで効率が半分になります。多くのアーキテクチャでは、GPUは、スレッドが同じ命令ポストブランチに到達すると、スレッドを単一のワープに収束させることでこれを解決しようとします。または、コンパイラーは、スレッドを収束させるか、GPUに指示する同期ポイントを明示的に配置します。

例えば:

if(a)
    x += z * w;
    q >>= p;
else if(c)
    y -= 3;
r += t;

スレッドは分岐する可能性が高い(異なる命令パス)ので、そのような場合r += t;、命令ポインタが再び同じ場所で収束する可能性があります。分岐は2つ以上の分岐でも発生する可能性があり、ワープの使用率はさらに低くなります。4つの分岐は32のスレッドが4つのワープに分割され、25%のスループット使用率になります。ただし、25%はプログラム全体のスループットを維持できないため、収束によりこれらの問題の一部を隠すことができます。

あまり洗練されていないGPUでは、他の問題が発生する可能性があります。分岐するのではなく、単にすべてのブランチを計算し、最後に出力を選択します。これは発散と同じように見える場合があります(どちらも1 / nスループット使用率を持っています)が、複製アプローチにはいくつかの大きな問題があります。

1つは電力の使用量です。ブランチが発生すると、はるかに多くの電力を使用します。これはモバイルgpusにとっては悪いことです。2つ目は、同じワープのスレッドが異なるパスを使用し、異なる命令ポインター(パスカルの時点で共有されている)を持つ場合にのみ、Nvidia gpusで分岐が発生することです。したがって、Nvidia GPUが32の倍数で発生したり、数十個の単一のワープでのみ発生する場合、Nvidia GPUで分岐が発生し、スループットの問題は発生しません。分岐が発生する可能性がある場合は、分岐するスレッドが少なくなる可能性が高く、分岐の問題は発生しません。

もう1つの小さな問題は、GPUとCPUを比較した場合、それらのメカニズムが占めるハードウェアの量が多く、予測メカニズムやその他の堅牢なブランチメカニズムがないことが多いためです。

実用的なGPUアーキテクチャの違いの例

次に、Stephanesの例を取り上げて、2つの理論的なアーキテクチャ上のブランチレスソリューションのアセンブリがどのようになるかを見てみましょう。

n = (a==b) ? x : y;

ステファンが言ったように、デバイスコンパイラが分岐に遭遇すると、命令を使用して、分岐ペナルティを持たない要素を「選択」することを決定する場合があります。これは、一部のデバイスでは次のようにコンパイルされることを意味します

cmpeq rega, regb
// implicit setting of comparison bit used in next part
choose regn, regx, regy

選択命令を持たない他の人には、

n = ((a==b))* x + (!(a==b))* y

次のようになります。

cmpeq rega regb
// implicit setting of comparison bit used in next part
mul regn regcmp regx
xor regcmp regcmp 1
mul regresult regcmp regy
mul regn regn regresult

これはブランチレスで同等ですが、より多くの命令を取ります。Stephanesの例はそれぞれのシステムでコンパイルされる可能性が高いため、最初のアーキテクチャのコンパイラが2番目の形式ではなくより高速なフォーム。


5

@Stephane Hockenhullの回答で述べたすべてのことに同意します。最後のポイントを展開するには:

特定のGLSLコンパイラまたは特定のGPUがどのように動作するかを、ベンチマークするまで事前に知ることはできません。

絶対に本当。さらに、この種の質問は非常に頻繁に出てくると思います。しかし実際には、フラグメントシェーダーがパフォーマンスの問題の原因であることはめったにありません。GPUからの状態の読み取りが多すぎる、バッファの交換が多すぎる、1回の描画呼び出しでの作業が多すぎるなどの問題が他の要因によって引き起こされることは、より一般的です。

言い換えれば、シェーダーのマイクロ最適化について心配する前に、アプリ全体のプロファイルを作成し、シェーダーがスローダウンの原因であることを確認してください。

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