内核中可能的调用与不太可能的调用之间是什么?在搜索内核源代码时,我发现了这些语句。
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
有人可以给它一些启示吗?
内核中可能的调用与不太可能的调用之间是什么?在搜索内核源代码时,我发现了这些语句。
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
有人可以给它一些启示吗?
Answers:
它们是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希望这样做,则最有可能的分支将不涉及跳转。
让我们反编译以查看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
是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它们可能会(a双关!)做同样的事情。