逆コンパイルして、GCC4.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.2x86_64Linuxでコンパイルおよび逆コンパイルします。
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属性を使用する方法彼らはおそらく(しゃれ!)同じことをします。