どうやら、三項および通常の if ステートメントの両方で機能します。
まず、次の 3 つのコード サンプルを見てみましょう。そのうちの 2 つは__builtin_expect
通常の場合と 3 項の場合の両方のスタイルで使用され、3 つ目はまったく使用されていません。
builtin.c:
int main()
{
char c = getchar();
const char *printVal;
if (__builtin_expect(c == 'c', 1))
{
printVal = "Took expected branch!\n";
}
else
{
printVal = "Boo!\n";
}
printf(printVal);
}
三項.c:
int main()
{
char c = getchar();
const char *printVal = __builtin_expect(c == 'c', 1)
? "Took expected branch!\n"
: "Boo!\n";
printf(printVal);
}
nobuiltin.c:
int main()
{
char c = getchar();
const char *printVal;
if (c == 'c')
{
printVal = "Took expected branch!\n";
}
else
{
printVal = "Boo!\n";
}
printf(printVal);
}
でコンパイルすると-O3
、3 つすべてが同じアセンブリになります。ただし、-O
(GCC 4.7.2 で) が省略されている場合、ternary.c と builtin.c の両方に同じアセンブリ リストがあります (重要な場合)。
ビルトイン:
.file "builtin.c"
.section .rodata
.LC0:
.string "Took expected branch!\n"
.LC1:
.string "Boo!\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call getchar
movb %al, 27(%esp)
cmpb $99, 27(%esp)
sete %al
movzbl %al, %eax
testl %eax, %eax
je .L2
movl $.LC0, 28(%esp)
jmp .L3
.L2:
movl $.LC1, 28(%esp)
.L3:
movl 28(%esp), %eax
movl %eax, (%esp)
call printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.7.2-4) 4.7.2"
.section .note.GNU-stack,"",@progbits
三元.s:
.file "ternary.c"
.section .rodata
.LC0:
.string "Took expected branch!\n"
.LC1:
.string "Boo!\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call getchar
movb %al, 31(%esp)
cmpb $99, 31(%esp)
sete %al
movzbl %al, %eax
testl %eax, %eax
je .L2
movl $.LC0, %eax
jmp .L3
.L2:
movl $.LC1, %eax
.L3:
movl %eax, 24(%esp)
movl 24(%esp), %eax
movl %eax, (%esp)
call printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.7.2-4) 4.7.2"
.section .note.GNU-stack,"",@progbits
一方、nobuiltin.c は次のことを行いません。
.file "nobuiltin.c"
.section .rodata
.LC0:
.string "Took expected branch!\n"
.LC1:
.string "Boo!\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call getchar
movb %al, 27(%esp)
cmpb $99, 27(%esp)
jne .L2
movl $.LC0, 28(%esp)
jmp .L3
.L2:
movl $.LC1, 28(%esp)
.L3:
movl 28(%esp), %eax
movl %eax, (%esp)
call printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.7.2-4) 4.7.2"
.section .note.GNU-stack,"",@progbits
関連部分:
基本的に、入力 char と の直接比較に基づくのではなく、CPU が 1 であると予測する可能性が高い結果 (ここでは単純な仮定) に基づいて、 の前に__builtin_expect
余分なコード ( sete %al
...) が実行されます。一方、nobuiltin.c の場合、そのようなコードは存在せず、「c」との比較の直後に/が続きます ( )。分岐予測は主に CPU で行われることを思い出してください。ここで GCC は、CPU 分岐予測子がどのパスが取られるかを想定するために単に「罠を仕掛けている」だけです (追加のコードと と の切り替えを介して、私は持っていませんが、 Intel の公式の最適化マニュアルでは、 vsでの最初の遭遇の処理について言及していないため、このソースje .L2
testl %eax, %eax
'c'
je
jne
cmp $99
je
jne
je
jne
分岐予測の場合は異なります!GCCチームが試行錯誤を経てこれに到達したとしか思えません)。
GCC の分岐予測を (CPU へのヒントを観察する代わりに) より直接的に見ることができるより良いテスト ケースがあると確信していますが、そのようなケースを簡潔に/簡潔にエミュレートする方法はわかりません。(推測: コンパイル中にループの展開が必要になる可能性があります。)