11

GCCマニュアルには、「if」ステートメントの条件全体の周りに__builtin_expect()が配置されている例のみが示されています。

また、GCCは、たとえば、三項演算子を使用したり、分岐コンテキストで使用されていないものであっても、任意の整数式で使用しても文句を言わないことに気付きました。

それで、私はその使用法の根本的な制約が実際に何であるか疑問に思います。

次のような三項演算で使用した場合、その効果は保持されますか?

int foo(int i)
{
  return __builtin_expect(i == 7, 1) ? 100 : 200;
}

そして、この場合はどうですか?

int foo(int i)
{
  return __builtin_expect(i, 7) == 7 ? 100 : 200;
}

そしてこれ:

int foo(int i)
{
  int j = __builtin_expect(i, 7);
  return j == 7 ? 100 : 200;
}
4

1 に答える 1

9

どうやら、三項および通常の 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 .L2testl %eax, %eax'c'jejnecmp $99jejnejejne分岐予測の場合は異なります!GCCチームが試行錯誤を経てこれに到達したとしか思えません)。

GCC の分岐予測を (CPU へのヒントを観察する代わりに) より直接的に見ることができるより良いテスト ケースがあると確信していますが、そのようなケースを簡潔に/簡潔にエミュレートする方法はわかりません。(推測: コンパイル中にループの展開が必要になる可能性があります。)

于 2013-02-09T05:57:06.950 に答える