はい、それは合理的であり、コンパイラ適切なシナリオでそれを活用することができます。あなたの実際の例では
、could_be
とvery_improbable
が実際に不可欠な変数である場合、コンパイラは本当にについて何を行うことができますので、述語の部分式にlikely
またはunlikely
マクロを挿入する任意のポイントであることをそこに行くされていませんこれをより速くする?コンパイラは、ブランチの結果に応じてif
ブロックを別々に整理できますが、very_improbably
が役に立たないと思われるだけで、テストするコードを生成する必要があります。
のは、コンパイラが、より多くの作業を行うことができます例を見てみましょう:ここ
extern int fn1();
extern int fn2();
extern int f(int x);
int test_likely(int a, int b) {
if (likely(f(a)) && unlikely(f(b)))
return fn1();
return fn2();
}
述語は引数付きf()
への2回の呼び出しで構成され、icc
はlikely
の4つの組み合わせの3のために異なるコードが生成されますそしてunlikely
:likely(f(a)) && likely(f(b))
ため
コードproduced:
test_likely(int, int):
push r15 #8.31
mov r15d, esi #8.31
call f(int) #9.7
test eax, eax #9.7
je ..B1.7 # Prob 5% #9.7
mov edi, r15d #9.23
call f(int) #9.23
test eax, eax #9.23
je ..B1.7 # Prob 5% #9.23
pop r15 #10.12
jmp fn1() #10.12
..B1.7: # Preds ..B1.4 ..B1.2
pop r15 #11.10
jmp fn2() #11.10
ここでは、両方の述部が真である可能性が高いため、icc
は、両方が真である場合に直線コードを生成し、いずれかが偽であると判断された場合は飛び越します。 unlikely(f(a)) && likely(f(b))
ため
コードproduced:
test_likely(int, int):
push r15 #8.31
mov r15d, esi #8.31
call f(int) #9.7
test eax, eax #9.7
jne ..B1.5 # Prob 5% #9.7
..B1.3: # Preds ..B1.6 ..B1.2
pop r15 #11.10
jmp fn2() #11.10
..B1.5: # Preds ..B1.2
mov edi, r15d #9.25
call f(int) #9.25
test eax, eax #9.25
je ..B1.3 # Prob 5% #9.25
pop r15 #10.12
jmp fn1() #10.12
さて、述語はおそらく偽であるので、icc
は、その場合のリターンに直結する定額コードを生成し、へB1.5
にラインの外にジャンプします述部を続行します。この場合、2番目のコール(f(b)
)がtrueになると予想され、tail-call〜fn1()
で終了するフォールスルーコードが生成されます。 2番目の呼び出しがfalseになると、最初のジャンプ(ラベルB1.3
)でfall-thoughの場合に既にアセンブルされている同じシーケンスにジャンプします。
これはunlikely(f(a)) && unlikely(f(b))
ために生成されたコードであることもが判明しました。この場合、コンパイラがコードの末尾を変更してjmp fn2()
をフォールスルーケースにすると想像できますが、そうではありません。これにより、以前のシーケンスをB1.3
で再利用することができなくなることがわかりました。でもこのコードを実行している可能性は低いので、すでにあるとは限りません。 likely(f(a)) && unlikely(f(b))
ため
コードproducedは:
test_likely(int, int):
push r15 #8.31
mov r15d, esi #8.31
call f(int) #9.7
test eax, eax #9.7
je ..B1.5 # Prob 5% #9.7
mov edi, r15d #9.23
call f(int) #9.23
test eax, eax #9.23
jne ..B1.7 # Prob 5% #9.23
..B1.5: # Preds ..B1.4 ..B1.2
pop r15 #11.10
jmp fn2() #11.10
..B1.7: # Preds ..B1.4
pop r15 #10.12
jmp fn1() #10.12
これは、そのようにブロックを、注文し直すように、第二述語に対する期待が現在偽であることを除いて、最初のケース(likely && likely
)に類似していますreturn fn2()
のケースは、フォールスルーケースです。
ので、コンパイラは間違いなく正確likely
とunlikely
情報を使用することができ、そして実際にそれが理にかなって:あなたは二つに上記のテストを解散if
文を連鎖している場合、それは驚くべきことではないので、それは、別々の分岐ヒントがうまくいくことはかなり明白です意味的に同等の&&
の使用には依然としてヒントがあります。ここで
は、あなたがここまでなった場合には、「全文」の治療を取得していないいくつかの他の注意事項です:私は例を説明するために
icc
を使用
- を、このテストのために、少なくとも両方
clang
gcc
は同じ基本的な最適化を行います(4つのケースのうち3つを別々にコンパイルします)。
- コンパイラがサブ述部の確率を知ることによって作ることができる1つの「明白な」最適化は、述語の順序を逆にすることです。たとえば、
likely(X) && unlikely(Y)
がある場合は、Y
の状態を最初に確認できます。これは、ショート・カット・チェックY を許可する可能性が高いためです。どうやら、単純な述語ではgccはmake this optimizationになりますが、これを行うにはiccまたはclangを同軸化できませんでした。 gccの最適化は明らかに非常に脆弱です。つまり、の方がはるかに良いとなるにもかかわらず、述語を少し変更するとdisappearsになります。
- コンパイラは、変換されたコードが、言語の意味に従って直接コンパイルされたかのように動作することを保証できない場合、最適化を行うことが制限されています。特に、操作に副作用がないことを証明できない限り、操作の順序を変更する機能は制限されています。述語を構造化するときは、そのことを覚えておいてください。もちろん
、コンパイラはX
とY
は副作用がないことが確認できたときに、これはを許可されているだけ、およびY
がはるかに高価にある場合には、効果ではないかもしれません(X
の評価の追加費用が高いため、Y
へのチェックを避けることのいずれかの利点が圧倒されるため)。
GCC-5.4には、「-Wall -O2 -march = x86-64 -mtune = generic -S'、一般的なAMD64/x86-64アーキテクチャのアセンブリを生成するには) ';生成されるコードに違いはありません。 –
Linuxカーネルの[likely()/ unlikely()マクロと重複している可能性があります。何が彼らの利益ですか?](http://stackoverflow.com/questions/109710/likely-unlikely-macros-in-the-linux-kernel-how-do-the-work-whats-their) –
@NominalAnimalあなたはどういう意味ですか?コードスニペットを作成してコンパイルしたことはありますか? – immortal