2016-08-18 2 views
6

私が持っている場合:()プリプロセッサマクロであれば、そうでない場合はチェーン

#define likely(x)  __builtin_expect((x),1) 
#define unlikely(x)  __builtin_expect((x),0) 

if (A) 
    return true; 
else if (B) 
    return false; 
... 
else if (Z) 
    return true; 
else 
    //this will never really happen!!!! 
    raiseError(); 
    return false; 

は私が最後の文ことを意味する可能性が高い()else if (likely(Z))のような最後の条件チェックの周りに置くことができます( else)は、コンパイラが前のチェックの分岐予測に影響を与えることはありませんか?

基本的に、分岐予測子ヒントを持つ単一の条件文がある場合、GCCはif-else ifブロック全体を最適化しようとしますか?

+0

確かに見つかる唯一の方法は、(最適化を有効にして)ビルドし、生成されたコードをチェックすることです。使用される可能性のあるものと無いものとで生成されたコードを比較する。 –

+0

他のすべての条件の周りに '恐らく'を置かないのはなぜですか? –

+0

@KerrekSBそれは私が避けたいものです。条件のすべてが真ではないという条件を除いて、すべての条件が同様に高い可能性があります。 – Leo

答えて

8

あなたは、これは明示しなければならない:

if (A) 
    return true; 
else if (B) 
    return true; 
... 
else if (Y) 
    return true; 
else { 
    if (likely(Z)) 
    return true; 

    raiseError(); 
    return false; 
} 

は今はっきりとあなたの意図を理解してコンパイラと他の分岐確率を再割り当てすることはありません。また、コードの可読性が向上しました。

P.S.私はあなたがLinuxカーネルがサイレント不可欠なキャストから保護するために行う方法でも可能性が高いとそう書き直すことをお勧め:一般的に

#define likely(x)  __builtin_expect(!!(x), 1) 
#define unlikely(x) __builtin_expect(!!(x), 0) 
2

を、GCCは、if文で条件文が真となることを前提と - そこに例外はありますが、それらは、文脈上の

g(int*): 
     testq %rdi, %rdi 
     je  .L6 
     jmp  t(int*) 
.L6: 
     xorl %eax, %eax 
     ret 

godbolt参照)

を:

extern int t(int*); 
int g(int* ip) { 
    if (!ip) 
    return 0; 
    return t(ip); 
} 

を生成しながら

extern int s(int); 

int f(int i) { 
    if (i == 0) 
    return 1; 
    return s(i); 
} 

f(int): 
     testl %edi, %edi 
     jne  .L4 
     movl $1, %eax 
     ret 
.L4: 
     jmp  s(int) 

を生成します

fでブランチがjne(条件が真と仮定する)であるのに対して、gでは条件は偽であるとみなされます。

は今、次のと比較:

我々は文脈要因のいずれかを参照してくださいここで

x(int, int*): 
     testq %rsi, %rsi 
     je  .L3   # first branch: assumed unlikely 
     movl $2, %eax 
     testl %edi, %edi 
     jne  .L12  # second branch: assumed likely 
     ret 
.L12: 
     pushq %rbx 
     movq %rsi, %rbx 
     call s(int) 
     movl %eax, %edx 
     movl $3, %eax 
     testl %edx, %edx 
     je  .L13  # third branch: assumed likely 
.L2: 
     popq %rbx 
     ret 
.L3: 
     movl $1, %eax 
     ret 
.L13: 
     movq %rbx, %rdi 
     call t(int*) 
     movl %eax, %edx 
     movl $4, %eax 
     testl %edx, %edx 
     jne  .L2  # fourth branch: assumed unlikely! 
     movq %rbx, %rdi 
     call t(int*) 
     popq %rbx 
     movl %eax, %edi 
     jmp  s(int) 

を生成

extern int s(int); 
extern int t(int*); 

int x(int i, int* ip) { 
    if (!ip) 
    return 1; 
    if (!i) 
    return 2; 
    if (s(i)) 
    return 3; 
    if (t(ip)) 
    return 4; 
    return s(t(ip)); 
} 

:GCCは、それがここにL2を再利用することができることを発見し、それをすることを決めたように最終的な条件付きではないと判断し、コードを少なくすることができます。

は、あなたが与えた例の組立見てみましょう:

#define likely(x)  __builtin_expect((x),1) 
#define unlikely(x)  __builtin_expect((x),0) 

extern void raiseError(); 

int f(int A, int B, int Z) 
{ 
    if (A) 
    return 1; 
    else if (B) 
    return 2; 
    else if (Z) 
    return 3; 

    raiseError(); 
    return -1; 
} 

アセンブリlooks like this:!Zが真であるとき、それはすでにかのように振る舞うます

f(int, int, int): 
     movl $1, %eax 
     testl %edi, %edi 
     jne  .L9 
     movl $2, %eax 
     testl %esi, %esi 
     je  .L11 
.L9: 
     ret 
.L11: 
     testl %edx, %edx 
     je  .L12  # branch if !Z 
     movl $3, %eax 
     ret 
.L12: 
     subq $8, %rsp 
     call raiseError() 
     movl $-1, %eax 
     addq $8, %rsp 
     ret 

注生成されたコード分岐することをZは可能性が高い。 Zがそうであると言えばどうなりますか?

#define likely(x)  __builtin_expect((x),1) 
#define unlikely(x)  __builtin_expect((x),0) 

extern void raiseError(); 

int f(int A, int B, int Z) 
{ 
    if (A) 
    return 1; 
    else if (B) 
    return 2; 
    else if (likely(Z)) 
    return 3; 

    raiseError(); 
    return -1; 
} 

we get

f(int, int, int): 
     movl $1, %eax 
     testl %edi, %edi 
     jne  .L9 
     movl $2, %eax 
     testl %esi, %esi 
     je  .L11 
.L9: 
     ret 
.L11: 
     movl $3, %eax # assume Z 
     testl %edx, %edx 
     jne  .L9   # but branch if Z 
     subq $8, %rsp 
     call raiseError() 
     movl $-1, %eax 
     addq $8, %rsp 
     ret 

ここでのポイントは、あなたがこれらのマクロを使用するときに慎重に、あなたが期待していた結果を得ることを確認する前に、慎重に後にコードを調べなければならないことである、とベンチマーク(例えばperfで)、プロセッサが生成しているコードと一致する予測をしていることを確認します。

関連する問題