カーネルで可能な呼び出しと不可能な呼び出しの違いは何ですか?カーネルソースコードの検索中に、次の文が見つかりました。
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
誰かがこれを説明できますか?
答え1
これはGCCのコンパイラのヒントです。条件文でブランチを実行できるかどうかをコンパイラに通知するために使用されます。これは、コンパイラが最も一般的な結果に最も適した方法でコードを書くのに役立ちます。
これらは次のように使用されます。
if (likely(some_condition)) {
// the compiler will try and make the code layout optimal for the case
// where some_condition is true, i.e. where this block is run
most_likely_action();
} else {
// this block is less frequently used
corner_case();
}
慎重に使用する必要があります(つまり、実際の四半期分析の結果に基づいています)。間違ったプロンプトはパフォーマンスを低下させます(明らかに)。
を検索すると、コードを最適化する方法のいくつかの例を簡単に見つけることができますGCC __builtin_expect
。このブログ投稿gcc最適化:__builtin_expect例えば、分解について詳しく説明する。
実行できる最適化の種類は、プロセッサごとに異なります。一般的なアイデアは、コードがどこからでも分岐/ジャンプしない場合、プロセッサは通常コードをより速く実行することです。線形性が高く、分岐の予測可能性が高いほど、実行速度が速くなります。 (たとえば、深いパイプラインを持つプロセッサの場合は特にそうです。)
したがって、コンパイラは、可能性の高いブランチにジャンプが含まれないようにコードをエクスポートします(たとえば、ターゲットCPUがジャンプを好む場合など)。
答え2
デコンパイルして、GCC 4.8が何をしているのか見てみましょう。
予期しない
#include "stdio.h"
#include "time.h"
int main() {
/* Use time to prevent it from being optimized away. */
int i = !time(NULL);
if (i)
printf("%d\n", 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 14 jne 24 <main+0x24>
10: ba 01 00 00 00 mov $0x1,%edx
15: be 00 00 00 00 mov $0x0,%esi
16: R_X86_64_32 .rodata.str1.1
1a: bf 01 00 00 00 mov $0x1,%edi
1f: e8 00 00 00 00 callq 24 <main+0x24>
20: R_X86_64_PC32 __printf_chk-0x4
24: bf 00 00 00 00 mov $0x0,%edi
25: R_X86_64_32 .rodata.str1.1+0x4
29: e8 00 00 00 00 callq 2e <main+0x2e>
2a: R_X86_64_PC32 puts-0x4
2e: 31 c0 xor %eax,%eax
30: 48 83 c4 08 add $0x8,%rsp
34: c3 retq
メモリ内の命令の順序は変更されません。まずprintf
、次にputs
returnですretq
。
期待を持って
今すぐ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 11 je 21 <main+0x21>
10: bf 00 00 00 00 mov $0x0,%edi
11: R_X86_64_32 .rodata.str1.1+0x4
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
21: ba 01 00 00 00 mov $0x1,%edx
26: be 00 00 00 00 mov $0x0,%esi
27: R_X86_64_32 .rodata.str1.1
2b: bf 01 00 00 00 mov $0x1,%edi
30: e8 00 00 00 00 callq 35 <main+0x35>
31: R_X86_64_PC32 __printf_chk-0x4
35: eb d9 jmp 10 <main+0x10>
printf
__printf_chk
(でコンパイル)は、関数の最後に移動し、puts
他の回答で述べたように、分岐予測を改善するために返されます。
したがって、基本的には次のようになります。
int i = !time(NULL);
if (i)
goto printf;
puts:
puts("a");
return 0;
printf:
printf("%d\n", i);
goto puts;
この最適化は役に立ちません-O0
。
__builtin_expect
しかし、それがないよりも速く実行される例を書くことに幸運があることを願っています。その当時、CPUは本当にスマートでした。。素直に頑張ったここにあります。。
C++20[[likely]]
と[[unlikely]]
C++20 は、次の C++ 組み込み関数を標準化しました。https://stackoverflow.com/questions/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement彼らはおそらく(愚かな意図!)のようなことをします。