GNU/Linux >> Linux の 問題 >  >> Linux

if else ステートメントでの GCC の __builtin_expect の利点は何ですか?

以下から生成されるアセンブリ コードを想像してみてください:

if (__builtin_expect(x, 0)) {
    foo();
    ...
} else {
    bar();
    ...
}

私はそれが次のようなものであるべきだと思います:

  cmp   $x, 0
  jne   _foo
_bar:
  call  bar
  ...
  jmp   after_if
_foo:
  call  foo
  ...
after_if:

bar という順序で命令が配置されていることがわかります。 大文字と小文字が foo の前にある ケース (C コードとは対照的に)。これにより、ジャンプが既にフェッチされた命令をスラッシングするため、CPU パイプラインをより有効に利用できます。

ジャンプが実行される前に、その下の命令 (bar ケース) がパイプラインにプッシュされます。 foo 以降 ジャンプする可能性は低いため、パイプラインをスラッシングする可能性は低いです。


逆コンパイルして、GCC 4.8 で何ができるか見てみましょう

Blagovest は、パイプラインを改善するために分岐反転について言及しましたが、現在のコンパイラは本当にそれを行っているのでしょうか?調べてみましょう!

__builtin_expect なし

#include "stdio.h"
#include "time.h"

int main() {
    /* Use time to prevent it from being optimized away. */
    int i = !time(NULL);
    if (i)
        puts("a");
    return 0;
}

GCC 4.8.2 x86_64 Linux でコンパイルおよび逆コンパイル:

gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o

出力:

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       75 0a                   jne    1a <main+0x1a>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1
  15:       e8 00 00 00 00          callq  1a <main+0x1a>
                    16: R_X86_64_PC32       puts-0x4
  1a:       31 c0                   xor    %eax,%eax
  1c:       48 83 c4 08             add    $0x8,%rsp
  20:       c3                      retq

メモリ内の命令順序は変更されていません:最初に puts そして retq 戻る。

__builtin_expect

if (i) を置き換えます と:

if (__builtin_expect(i, 0))

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       74 07                   je     17 <main+0x17>
  10:       31 c0                   xor    %eax,%eax
  12:       48 83 c4 08             add    $0x8,%rsp
  16:       c3                      retq
  17:       bf 00 00 00 00          mov    $0x0,%edi
                    18: R_X86_64_32 .rodata.str1.1
  1c:       e8 00 00 00 00          callq  21 <main+0x21>
                    1d: R_X86_64_PC32       puts-0x4
  21:       eb ed                   jmp    10 <main+0x10>

puts 関数の最後、retq に移動されました。 戻る!

新しいコードは基本的に次のものと同じです:

int i = !time(NULL);
if (i)
    goto puts;
ret:
return 0;
puts:
puts("a");
goto ret;

この最適化は -O0 では行われませんでした .

しかし、__builtin_expect でより高速に実行される例を書いて頑張ってください。 当時の CPU は本当にスマートです。私の単純な試みはここにあります。

C++20 [[likely]][[unlikely]]

C++20 はこれらの C++ ビルトインを標準化しました:if-else ステートメントで C++20 の like/unlikely 属性を使用する方法 それらはおそらく (しゃれ!) 同じことをします.


__builtin_expect の考え方 通常は式が c に評価されることをコンパイラに伝え、コンパイラがその場合に最適化できるようにすることです。

誰かが自分は賢いと思っていて、これを行うことで物事をスピードアップしていると思います。

残念ながら、状況が十分に理解されていない場合を除きます (彼らはそのようなことをしていない可能性が高いです)、それは事態を悪化させた可能性があります.ドキュメントには次のようにも書かれています:

<ブロック引用>

一般に、これには実際のプロファイル フィードバックを使用することをお勧めします (-fprofile-arcs )、プログラマーは自分のプログラムが実際にどのように実行されるかを予測するのが苦手であることで有名です。ただし、このデータを収集するのが難しいアプリケーションもあります。

通常、__builtin_expect は使用しないでください。 例外:

  • 非常に深刻なパフォーマンスの問題があります
  • システムのアルゴリズムを適切に最適化済み
  • 特定のケースが最も可能性が高いという主張を裏付けるパフォーマンス データがあります

Linux
  1. 空きスペースはどうなりましたか?

  2. Gccを6.3バージョンに更新しますか?

  3. LD_PRELOAD トリックとは何ですか?

  1. POSIX とはどういう意味ですか?

  2. GCC のデフォルトのインクルード ディレクトリとは?

  3. CFSのvruntimeの概念は何ですか

  1. Bashでの$#の使用は何ですか

  2. 画面のデフォルトのパスワードは何ですか?

  3. ゴールドリンカーとは何ですか?