24

別の質問に答えている間、私はこれについて興味を持ちました。私はそれをよく知っています

if( __builtin_expect( !!a, 0 ) ) {
    // not likely
} else {
    // quite likely
}

プロセッサへのヒント/アセンブリコードの順序の変更/ある種の魔法の行に沿って何かを行うことにより、「かなり可能性の高い」分岐を(一般的に)より高速にします。(誰かがその魔法を明確にすることができれば、それも素晴らしいでしょう).

しかし、これは a) インライン if、b) 変数、c) 0 と 1 以外の値に対して機能しますか? すなわち意志

__builtin_expect( !!a, 0 ) ? /* unlikely */ : /* likely */;

また

int x = __builtin_expect( t / 10, 7 );
if( x == 7 ) {
    // likely
} else {
    // unlikely
}

また

if( __builtin_expect( a, 3 ) ) {
    // likely
    // uh-oh, what happens if a is 2?
} else {
    // unlikely
}

効果はありますか?そして、これらすべては対象となるアーキテクチャに依存するのでしょうか?

4

2 に答える 2

20

GCC のドキュメントを読みましたか?

組み込み関数: long __builtin_expect (long exp, long c)

__builtin_expect を使用して、コンパイラに分岐予測情報を提供できます。一般に、これには実際のプロファイル フィードバック (-fprofile-arcs) を使用することをお勧めします。これは、プログラマーが自分のプログラムが実際にどのように動作するかを予測するのが苦手なことで知られているためです。ただし、このデータを収集するのが難しいアプリケーションもあります。

戻り値は exp の値であり、整数式でなければなりません。組み込みのセマンティクスは、exp == c であることが期待されるということです。例えば:

if (__builtin_expect (x, 0))
    foo ();

x が 0 であると予想されるため、foo を呼び出す必要がないことを示します。exp の整数式に制限されているため、次のような構造を使用する必要があります。

if (__builtin_expect (ptr != NULL, 1))
    foo (*ptr);

ポインターまたは浮動小数点値をテストする場合。

これを少し説明すると... __builtin_expect は、プログラムがどのブランチを取る可能性が高いと思われるかを伝えるのに特に役立ちます。コンパイラがその洞察をどのように使用できるかを尋ねると、次のコードを検討してください。

if (x == 0)
    return 10 * y;
else
    return 39;

マシンコードでは、通常、CPU は別の行に「移動」するように要求できます (これには時間がかかり、CPU によっては、他の実行の最適化が妨げられる場合があります。つまり、マシンコードのレベルの下にあります。たとえば、httpの下のブランチの見出しを参照してください。 //en.wikipedia.org/wiki/Instruction_pipeline )、または他のコードを呼び出すことができますが、true と false の両方のコードが等しいという if/else の概念は実際にはありません...コードを見つけるために分岐する必要がありますどちらか一方。これを行う方法は、基本的に、疑似コードで次のとおりです。

test whether x is 0
if it was goto else_return_39
return 10 * y
else_return_39:
return 39

ほとんどの CPU は、単に にフォールスルーするよりもラベルに到達gotoするのが遅いため、「true」ブランチのコードは false ブランチのコードよりも速く到達します。もちろん、マシン コードは x が0 でないかどうかをテストし、最初に「偽」のコード ( ) を配置して、パフォーマンス特性を逆にすることができます。else_return_39:return 10 * yreturn 39

これは __builtin_expect が制御するものです。コンパイラーに true または false 分岐を配置するように指示することができ、そこに到達するのに必要な分岐が少なくてすみます。これにより、パフォーマンスがわずかに向上します。

しかし、これは a) インライン if、b) 変数、c) 0 と 1 以外の値に対して機能しますか?

a) 周囲の関数がインライン化されているかどうかによって、ifステートメントが表示される場所での分岐の必要性は変わりません (オプティマイザーが、ifステートメント テストが常に実行されるtruefalse、1 つの分岐のみが実行されないという条件を認識しない限り)。したがって、インライン コードにも同様に適用できます。

[ あなたのコメントは、あなたが条件式に興味を持っていたことを示してa ? b : cます。さらなる探求の基礎]

b) 変数 - あなたが仮定した:

int x = __builtin_expect( t / 10, 7 );
if( x == 7 ) {

それはうまくいきません - コンパイラは、そのような期待を変数に関連付けて、次に anifが見られたときにそれらを記憶する義務はありません。これを確認するには (gcc 3.4.4 で行ったように) を使用gcc -Sして、アセンブリ言語の出力を生成します。期待値に関係なく、アセンブリは変更されません。

c) 0 と 1 以外の値

整数 ( long) 値で機能するので、そうです。上記のドキュメントの最後の段落では、具体的には次のように説明されています。

次のような構造を使用する必要があります

if (__builtin_expect (ptr != NULL, 1))
    foo (*ptr);

ポインターまたは浮動小数点値をテストする場合。

なんで?ポインタの型が よりも大きい場合long、呼び出し__builtin_conversion(long, long)によって重要度の低いビットの一部が効果的に切り捨てられ、残りをテストに組み込むことができなくなります。同様に、浮動小数点値は long よりも大きい場合があり、変換によって期待どおりの結果が得られません。ptr != NULL( trueconverts to 1L and to 0)などのブール式を使用するとfalse、意図した結果が確実に得られます。

于 2013-03-18T01:42:59.363 に答える
15

しかし、これは a) インライン if、b) 変数、c) 0 と 1 以外の値に対して機能しますか?

分岐を決定するために使用される式コンテキストに対して機能します。

だから、 a) はい。b) いいえ。 c) はい。

そして、これらすべては対象となるアーキテクチャに依存するのでしょうか?

はい!

命令パイプラインを使用するアーキテクチャを活用することで、CPU は現在の命令が完了する前に次の命令の処理を開始できます。

(誰かがその魔法を明確にすることができれば、それも素晴らしいでしょう).

(「分岐予測」はこの説明を複雑にするため、意図的に省略しています)

if ステートメントに似たコードは、式によって CPU がプログラム内の別の場所にジャンプする可能性があることを意味します。これらのジャンプは、CPU の命令パイプラインにあるものを無効にします。

__builtin_expect(保証なしに) gcc がコードをアセンブルしようとすることを許可するため、可能性の高いシナリオでは、代替よりもジャンプが少なくなります。

于 2013-03-18T01:28:41.237 に答える