《C语言杂记》编译优化之__builtin_expect
Posted Bruceoxl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《C语言杂记》编译优化之__builtin_expect相关的知识,希望对你有一定的参考价值。
最近在研究压缩算法的时候,看到了以下代码:
#define LIKELY(c) (__builtin_expect(!!(c), 1))
#define UNLIKELY(c) (__builtin_expect(!!(c), 0))
在好奇心的驱使下,我决定一探究竟。
1 __builtin_expect是啥
其实可以从宏定义就能猜个大概,顾名思义,LIKELY ()指“很有可能”之意,而UNLIKELY ()指“不太可能”之意。这两个有啥用处呢,可以先看看GCC官方的解释。
Built-in Function: long __builtin_expect (long exp, long c)
You may use __builtin_expect to provide the compiler with branch prediction information. In general, you should prefer to use actual profile feedback for this (-fprofile-arcs), as programmers are notoriously bad at predicting how their programs actually perform. However, there are applications in which this data is hard to collect.
The return value is the value of exp, which should be an integral expression. The semantics of the built-in are that it is expected that exp == c. For example:
if (__builtin_expect (x, 0))
foo ();
indicates that we do not expect to call foo, since we expect x to be zero. Since you are limited to integral expressions for exp, you should use constructions such as
if (__builtin_expect (ptr != NULL, 1))
foo (*ptr);
when testing pointer or floating-point values.
For the purposes of branch prediction optimizations, the probability that a __builtin_expect expression is true is controlled by GCC’s builtin-expect-probability parameter, which defaults to 90%.
You can also use __builtin_expect_with_probability to explicitly assign a probability value to individual expressions. If the built-in is used in a loop construct, the provided probability will influence the expected number of iterations made by loop optimizations.
大概意思就是__buildin_except
向编译器提供分支预测信息,根据条件跳转(JMP)的预期值,按正确地顺序生成汇编代码,把“很有可能发生”的条件分支放在顺序执行指令段,从而帮助编译器进行代码优化,但是程序中的跳转指令会打乱CPU流水线。因此,减少程序的跳转次数可以提高程序的执行效率,__buildin_except
的作用也就在于此。
使用__buildin_except
定义LIKELY
和UNLIKELY
宏,分别代表bool型变量或表达式有很大可能性为真或者很大可能性为假。
2 __builtin_expect函数说明
在Linux内核代码和一些追求效率的应用中都能看到__buildin_except
的身影。
在Linux内核中的include/linux/compiler.h其宏定义如下:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
以上两个宏定义核心就是__builtin_expect
函数,因此需要先搞清楚__builtin_expect
函数。
函数__builtin_expect()
是GCC v2.96版本引入的,其声明如下:
long __builtin_expect(long exp, long c);
① exp,exp为一个整型表exp 为一个整型表达式,例如: (ptr != NULL)。
② c,c 必须是一个编译期常量, 不能使用变量。
以上代码中‘!!’是为了把表达式x转化成0或者1。
3 __builtin_expect使用方法
LIKELY ()
和UNLIKELY ()
通常和if连用,下面通过实例说明。
/**
******************************************************************************
* @file main.c
* @author BruceOu
* @version V1.0
* @date 2021-09-06
* @blog https://blog.bruceou.cn/
* @Official Accounts 嵌入式实验楼
* @brief
******************************************************************************
*/
/**Include*********************************************************************/
#include <stdio.h>
#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
/**
* @brief likely
* @param x
* @retval x
*/
long likely_test(long x)
{
if(LIKELY(x))
{
x = 9;
}
else
{
x = 10;
}
return x;
}
/**
* @brief unlikely
* @param x
* @retval x
*/
long unlikely_test(long x)
{
if(UNLIKELY(x))
{
x = 9;
}
else
{
x = 10;
}
return x;
}
/**
* @brief normal
* @param x
* @retval x
*/
long normal_test(long x)
{
if(x)
{
x = 9;
}
else
{
x = 10;
}
return x;
}
/**
* @brief main
* @param None
* @retval int
*/
int main()
{
return 0;
}
$gcc -fprofile-arcs -O2 -c main.c
【注】参数 ‘-fprofile-arcs’ 来收集程序运行的执行流程和分支走向的实际反馈信息。
$objdump -S main.o
【注】参数 ‘S’ 表示显示源代码和反汇编代码,当然使用‘-d’也是可以的,只‘-S’参数包含‘-d’的内容,objdump的更多参数可查看附录。
反编译结果如下:
bruceou@ubuntu:~$ objdump -S main.o
main.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <likely_test>:
0: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 8 <likely_test+0x8>
7: 01
## 判断传入的参数x是否为0。若结果为0,ZF=1语句je执行跳转
8: 48 85 ff test %rdi,%rdi
b: b8 09 00 00 00 mov $0x9,%eax
10: 74 06 je 18 <likely_test+0x18>
## 将堆栈地址从堆栈弹出到ret来返回%rip(在32位模式下返回的是%eip)
12: f3 c3 repz retq
14: 0f 1f 40 00 nopl 0x0(%rax)
18: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 20 <likely_test+0x20>
1f: 01
20: b8 0a 00 00 00 mov $0xa,%eax
25: c3 retq
26: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
2d: 00 00 00
0000000000000030 <unlikely_test>:
30: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 38 <unlikely_test+0x8>
37: 01
38: 48 85 ff test %rdi,%rdi
3b: 75 13 jne 50 <unlikely_test+0x20>
3d: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 45 <unlikely_test+0x15>
44: 01
45: b8 0a 00 00 00 mov $0xa,%eax
4a: c3 retq
4b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
50: b8 09 00 00 00 mov $0x9,%eax
55: c3 retq
56: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
5d: 00 00 00
0000000000000060 <normal_test>:
60: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 68 <normal_test+0x8>
67: 01
68: 48 85 ff test %rdi,%rdi
6b: b8 09 00 00 00 mov $0x9,%eax
70: 75 0d jne 7f <normal_test+0x1f>
72: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 7a <normal_test+0x1a>
79: 01
7a: b8 0a 00 00 00 mov $0xa,%eax
7f: f3 c3 repz retq
Disassembly of section .text.startup:
0000000000000000 <main>:
0: 48 83 05 00 00 00 00 addq $0x1,0x0(%rip) # 8 <main+0x8>
7: 01
8: 31 c0 xor %eax,%eax
a: c3 retq
b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
0000000000000010 <_GLOBAL__sub_I_65535_0_likely_test>:
10: bf 00 00 00 00 mov $0x0,%edi
15: e9 00 00 00 00 jmpq 1a <_GLOBAL__sub_I_65535_0_likely_test+0xa>
从前面的汇编代码可以看出, normal_test()函数只有一个返回值,而优化后的函数会在赋值后立即返回,从而避免跳转。这样做的缺点是造成了代码冗余,但提升了效率。
【附】objdump命令
objdump命令是用查看目标文件或者可执行的目标文件的构成的gcc工具。objdump可以让你更多地了解二进制文件可能带有的附加信息。
objdump语法格式:
objdump [参数] [文件]
objdump常用参数:
欢迎访问我的网站
BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
BruceOu的知乎
欢迎订阅我的微信公众号
以上是关于《C语言杂记》编译优化之__builtin_expect的主要内容,如果未能解决你的问题,请参考以下文章
C语言中的__attribute__宏定义之section属性
《C语言杂记》C语言异常处理之 setjmp()和longjmp()