C语言进阶——likely和unlikely

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。

1 定义

  1 #define likely(x) __builtin_expect(!!(x), 1)
  2 #define unlikely(x) __builtin_expect(!!(x), 0)

__builtin_expect是编译器内建函数,原型为long __builtin_expect (long exp, long c)。该函数并不会改变exp的值,但是可以对if-else分支或者if分支结构进行优化。likely代表if分支大概率会发生,unlikely代表if分支大概率不会发生。

Tips: !!是C语言中处理逻辑表达式的一个技巧。因为C语言中没有布尔变量,所以布尔值是用整形来代替的,0为假,非0为真。当x为0时,!(x)为1,!!(x)为0,!!的运算没有什么意义;但当x为非0时(比如100),!(x)为0,!!(x)为1,这样就达到了将非0值(比如100)全部都映射为1的效果。

通过分析发现,unlikely的定义其实是可以不使用!!运算符的。

2 应用场景

总的来说,对代码运行效率有要求的if-else或if分支就应该使用likely或unlikely优化选项。

3 注意事项

  • likely和unlikely的概率判断务必准确,不要写反了,否则非但不能提升运行效率,反而会起到反作用。
  • 选择表达式时要选择编译阶段编译器无法推测出真假的表达式,否则优化不起作用。
  • 编译时需要至少使用-O2选项,否则优化不起作用。

4 作用机理

4.1 理论

使用likely或unlikely为什么会起到提升代码运行效率的优化效果呢?

主要的作用机理有以下2点:

  • gcc编译器在编译生成汇编代码时会在编译选项的引导下调整if分支内代码的位置,如果是likely修饰过的就调整到前面,如果是unlikely修饰过的就调整到后面。放到前面的代码可以节省跳转指令带来的时间开销,从而达到提升效率的目的。
  • 当代CPU都有ICache和流水线机制,在运行当前这条指令时,ICache会预读取后面的指令,以提升运行效率。但是如果条件分支的结果是跳转到了其他指令,那预取的下一条指令(有的CPU设计是4级流水,也就是4条指令)就没用了,这样就降低了流水线的效率。如果使用likely和unlikely来指导编译器总是将大概率执行的代码放在靠前的位置,就可以大大提高预取值的命中率,从而达到提升效率的目的。
4.2 实践

下面通过一个小栗子来感受一下likely和unlikely的行为。

期间使用的工具有gcc和objdump。涉及到的指令如下:

# 编译生成a.out,注意使用-O2选项,否则不生效
gcc -O2 test.c
# 根据生成的a.out生成反汇编代码
objdump -CS a.out > objdump.txt

Tips: objdump命令是用查看目标文件或者可执行的目标文件的构成的gcc工具

(1)-d:反汇编目标文件中包含的可执行指令。

(2)-S:混合显示源码和汇编代码,前提是在编译目标文件时加上-g,否则相当于-d。

(3)-C:一般针对C++语言,用来更友好地显示符号名。

4.2.1 不使用likely或unlikely选项
  /* 未使用likely或unlikely选项 */
  4 int main(int argc, char *argv[])
  5 {  
  6         int i = atoi(argv[1]); /* init i with the value that GCC can't optimize */
  7 
  8         if (i > 0){
  9                 i--;
 10         }else{
 11                 i++;
 12         }
 13 
 14         return i;
 15 }
! 未使用likely或unlikely的反汇编代码
 45 08048320 <main>:
 46  8048320:       55                      push   %ebp
 47  8048321:       89 e5                   mov    %esp,%ebp
 48  8048323:       83 e4 f0                and    $0xfffffff0,%esp
 49  8048326:       83 ec 10                sub    $0x10,%esp
 50  8048329:       8b 45 0c                mov    0xc(%ebp),%eax
 51  804832c:       8b 40 04                mov    0x4(%eax),%eax
 52  804832f:       89 04 24                mov    %eax,(%esp)
 53  8048332:       e8 d9 ff ff ff          call   8048310 <atoi@plt>
 54  8048337:       c9                      leave
 55  8048338:       8d 50 ff                lea    -0x1(%eax),%edx !注意到if分支内容在前
 56  804833b:       85 c0                   test   %eax,%eax
 57  804833d:       8d 48 01                lea    0x1(%eax),%ecx  !注意到else分支内容在后
 58  8048340:       0f 4e d1                cmovle %ecx,%edx
 59  8048343:       89 d0                   mov    %edx,%eax
 60  8048345:       c3                      ret

4.2.2 使用likely选项
  1 #define likely(x) __builtin_expect(!!(x), 1)
  2 #define unlikely(x) __builtin_expect(!!(x), 0)
  3 
  4 int main(int argc, char *argv[])
  5 {  
  6         int i = atoi(argv[1]); /* init i with the value that GCC can't optimize */
  7 
  8         if (likely(i > 0)){
  9                 i--;
 10         }else{
 11                 i++;
 12         }
 13 
 14         return i;
 15 }
 ! 使用likely后的反汇编代码
 45 08048320 <main>:
 46  8048320:       55                      push   %ebp
 47  8048321:       89 e5                   mov    %esp,%ebp
 48  8048323:       83 e4 f0                and    $0xfffffff0,%esp
 49  8048326:       83 ec 10                sub    $0x10,%esp
 50  8048329:       8b 45 0c                mov    0xc(%ebp),%eax
 51  804832c:       8b 40 04                mov    0x4(%eax),%eax
 52  804832f:       89 04 24                mov    %eax,(%esp)
 53  8048332:       e8 d9 ff ff ff          call   8048310 <atoi@plt>
 54  8048337:       85 c0                   test   %eax,%eax
 55  8048339:       7e 05                   jle    8048340 <main+0x20>
 56  804833b:       83 e8 01                sub    $0x1,%eax !注意到if分支内容在前
 57  804833e:       c9                      leave
 58  804833f:       c3                      ret
 59  8048340:       83 c0 01                add    $0x1,%eax !注意到else分支内容在后
 60  8048343:       c9                      leave
 61  8048344:       c3                      ret

4.2.3 使用unlikely选项
  1 #define likely(x) __builtin_expect(!!(x), 1)
  2 #define unlikely(x) __builtin_expect(!!(x), 0)
  3 
  4 int main(int argc, char *argv[])
  5 {  
  6         int i = atoi(argv[1]); /* init i with the value that GCC can't optimize */
  7 
  8         if (unlikely(i > 0)){
  9                 i--;
 10         }else{
 11                 i++;
 12  
 13 
 14         return i;
 15 }
 ! 使用unlikely选项的反汇编结果
 45 08048320 <main>:
 46  8048320:       55                      push   %ebp
 47  8048321:       89 e5                   mov    %esp,%ebp
 48  8048323:       83 e4 f0                and    $0xfffffff0,%esp
 49  8048326:       83 ec 10                sub    $0x10,%esp
 50  8048329:       8b 45 0c                mov    0xc(%ebp),%eax
 51  804832c:       8b 40 04                mov    0x4(%eax),%eax
 52  804832f:       89 04 24                mov    %eax,(%esp)
 53  8048332:       e8 d9 ff ff ff          call   8048310 <atoi@plt>
 54  8048337:       85 c0                   test   %eax,%eax
 55  8048339:       7f 05                   jg     8048340 <main+0x20>
 56  804833b:       83 c0 01                add    $0x1,%eax !注意到else分支内容被提前了
 57  804833e:       c9                      leave
 58  804833f:       c3                      ret
 59  8048340:       83 e8 01                sub    $0x1,%eax !注意到if分支内容被后移了
 60  8048343:       c9                      leave
 61  8048344:       c3                      ret

<完>

  • 23
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
C语言中,UNLIKELY是一个宏定义,用于提示编译器某个条件的发生概率较低。它一般用于if语句中,让编译器优化代码以提高性能。UNLIKELY可以与条件表达式结合使用,以告诉编译器这个条件很少会满足,从而优化代码的执行路径。通常,UNLIKELY用于if语句中的条件表达式,使得编译器更倾向于执行if语句中的else分支,以提高代码的执行效率。!!运算符是C语言中处理逻辑表达式的一个技巧,用于将非零值转换为1。当变量x为非零值时,!!(x)返回1,当x为零时,!!(x)返回0。这个技巧常用于将条件表达式的结果映射为布尔值。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [C语言技巧:有if时使用likelyunlikely让代码运行更快](https://blog.csdn.net/freestep96/article/details/128771498)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [C语言进阶——likelyunlikely](https://blog.csdn.net/weixin_44873133/article/details/107302688)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿越临界点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值