likely() and unlikely()

本文介绍了GCC编译器中的likely和unlikely宏如何帮助优化分支预测。通过具体示例展示了这些宏如何指导编译器生成更高效的代码,避免不必要的流水线刷新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Quote from : http://kernelnewbies.org/FAQ/LikelyUnlikely
  1. bvl = bvec_alloc(gfp_mask, nr_iovecs, &idx);
  2. if (unlikely(!bvl)) {
  3.   mempool_free(bio, bio_pool);
  4.   bio = NULL;
  5.   goto out;
  6. }
In fact, these functions are hints for the compiler that allows it to correctly optimize the branch, by knowing which is the likeliest one. The definitions of these macros, found in include/linux/compiler.h are the following :
  1. #define likely(x)       __builtin_expect(!!(x), 1)
  2. #define unlikely(x)     __builtin_expect(!!(x), 0)
The GCC documentation explains the role of __builtin_expect() :
  1.  -- Built-in Function: long __builtin_expect (long EXP, long C)
  2.      You may use `__builtin_expect' to provide the compiler with branch
  3.      prediction information.  In general, you should prefer to use
  4.      actual profile feedback for this (`-fprofile-arcs'), as
  5.      programmers are notoriously bad at predicting how their programs
  6.      actually perform.  However, there are applications in which this
  7.      data is hard to collect.
  8.      The return value is the value of EXP, which should be an integral
  9.      expression.  The value of C must be a compile-time constant.  The
  10.      semantics of the built-in are that it is expected that EXP == C.
  11.      For example:
  12.           if (__builtin_expect (x, 0))
  13.             foo ();
  14.      would indicate that we do not expect to call `foo', since we
  15.      expect `x' to be zero.  Since you are limited to integral
  16.      expressions for EXP, you should use constructions such as
  17.           if (__builtin_expect (ptr != NULL, 1))
  18.             error ();
  19.      when testing pointer or floating-point values.

How does it optimize things ?

It optimizes things by ordering the generated assembly code correctly, to optimize the usage of the processor pipeline. To do so, they arrange the code so that the likeliest branch is executed without performing any jmp instruction (which has the bad effect of flushing the processor pipeline).

To see how it works, let's compile the following simple C user space program with gcc -O2 :
  1. #define likely(x)    __builtin_expect(!!(x), 1)
  2. #define unlikely(x)  __builtin_expect(!!(x), 0)
  3. int main(char *argv[], int argc)
  4. {
  5.    int a;
  6.    /* Get the value from somewhere GCC can't optimize */
  7.    a = atoi (argv[1]);
  8.    if (unlikely (a == 2))
  9.       a++;
  10.    else
  11.       a--;
  12.    printf ("%d/n", a);
  13.    return 0;
  14. }
Now, disassemble the resulting binary using objdump -S (comments added by me) :
  1. 080483b0 <main>:
  2.  // Prologue
  3.  80483b0:       55                      push   %ebp
  4.  80483b1:       89 e5                   mov    %esp,%ebp
  5.  80483b3:       50                      push   %eax
  6.  80483b4:       50                      push   %eax
  7.  80483b5:       83 e4 f0                and    $0xfffffff0,%esp
  8.  //             Call atoi()
  9.  80483b8:       8b 45 08                mov    0x8(%ebp),%eax
  10.  80483bb:       83 ec 1c                sub    $0x1c,%esp
  11.  80483be:       8b 48 04                mov    0x4(%eax),%ecx
  12.  80483c1:       51                      push   %ecx
  13.  80483c2:       e8 1d ff ff ff          call   80482e4 <atoi@plt>
  14.  80483c7:       83 c4 10                add    $0x10,%esp
  15.  //             Test the value
  16.  80483ca:       83 f8 02                cmp    $0x2,%eax
  17.  //             --------------------------------------------------------
  18.  //             If 'a' equal to 2 (which is unlikely), then jump,
  19.  //             otherwise continue directly, without jump, so that it
  20.  //             doesn't flush the pipeline.
  21.  //             --------------------------------------------------------
  22.  80483cd:       74 12                   je     80483e1 <main+0x31>
  23.  80483cf:       48                      dec    %eax
  24.  //             Call printf
  25.  80483d0:       52                      push   %edx
  26.  80483d1:       52                      push   %edx
  27.  80483d2:       50                      push   %eax
  28.  80483d3:       68 c8 84 04 08          push   $0x80484c8
  29.  80483d8:       e8 f7 fe ff ff          call   80482d4 <printf@plt>
  30.  //             Return 0 and go out.
  31.  80483dd:       31 c0                   xor    %eax,%eax
  32.  80483df:       c9                      leave
  33.  80483e0:       c3                      ret
Now, in the previous program, replace the unlikely() by a likely(), recompile it, and disassemble it again (again, comments added by me) :

  1. 080483b0 <main>:
  2.  //             Prologue
  3.  80483b0:       55                      push   %ebp
  4.  80483b1:       89 e5                   mov    %esp,%ebp
  5.  80483b3:       50                      push   %eax
  6.  80483b4:       50                      push   %eax
  7.  80483b5:       83 e4 f0                and    $0xfffffff0,%esp
  8.  //             Call atoi()
  9.  80483b8:       8b 45 08                mov    0x8(%ebp),%eax
  10.  80483bb:       83 ec 1c                sub    $0x1c,%esp
  11.  80483be:       8b 48 04                mov    0x4(%eax),%ecx
  12.  80483c1:       51                      push   %ecx
  13.  80483c2:       e8 1d ff ff ff          call   80482e4 <atoi@plt>
  14.  80483c7:       83 c4 10                add    $0x10,%esp
  15.  //             --------------------------------------------------
  16.  //             If 'a' equal 2 (which is likely), we will continue
  17.  //             without branching, so without flusing the pipeline. The
  18.  //             jump only occurs when a != 2, which is unlikely.
  19.  //             ---------------------------------------------------
  20.  80483ca:       83 f8 02                cmp    $0x2,%eax
  21.  80483cd:       75 13                   jne    80483e2 <main+0x32>
  22.  //             Here the a++ incrementation has been optimized by gcc
  23.  80483cf:       b0 03                   mov    $0x3,%al
  24.  //             Call printf()
  25.  80483d1:       52                      push   %edx
  26.  80483d2:       52                      push   %edx
  27.  80483d3:       50                      push   %eax
  28.  80483d4:       68 c8 84 04 08          push   $0x80484c8
  29.  80483d9:       e8 f6 fe ff ff          call   80482d4 <printf@plt>
  30.  //             Return 0 and go out.
  31.  80483de:       31 c0                   xor    %eax,%eax
  32.  80483e0:       c9                      leave
  33.  80483e1:       c3                      ret

How should I use it ?

You should use it only in cases when the likeliest branch is very very very likely, or when the unlikeliest branch is very very very unlikely.
<think>好的,我现在要解决用户关于likelyunlikely在编程中的含义及用法的问题。首先,我需要回忆自己之前学过的相关知识,并结合提供的引用内容来整理答案。 用户提到的这几个引用中,引用[1]提到了`__builtin_expect(EXP, N)`的用法,说明likelyunlikely是基于这个内置函数的宏,用来告诉编译器条件判断的结果更可能是什么。引用[2]指出这是Linux内核中的优化手段,用于条件分支的性能优化。引用[3]则具体解释了宏的用法,分别标记条件为真或假的可能性,帮助编译器优化代码布局。 首先,我需要解释likelyunlikely的基本含义,它们的作用是给编译器提供分支预测的信息,从而生成更高效的机器码。比如,使用likely告诉编译器这个条件很可能为真,那么编译器可能会将更可能执行的代码放在前面,减少跳转带来的性能损耗。 接下来,应该详细说明它们的实现方式,即通过GCC的`__builtin_expect`函数,将条件的期望值传递给编译器。例如,`likely(x)`实际上就是`__builtin_expect(!!(x), 1)`,而unlikely则是期望值为0。 然后,需要举例说明在代码中如何使用这两个宏,比如在条件判断语句中包裹条件表达式,帮助编译器优化分支预测。例如,在错误处理或者罕见情况的条件判断时使用unlikely,而在正常流程的条件判断时使用likely。 同时,应该提到使用这两个宏的注意事项,比如不要滥用,只在确实有明确倾向性的分支中使用,否则可能导致性能下降。另外,需要指出它们主要适用于GCC编译器,其他编译器可能有不同的实现方式。 最后,根据用户提供的示例中的结构,生成相关问题,比如优化原理、适用场景、与其他优化方法的对比等。 检查是否有遗漏的部分,比如是否涵盖了定义、实现原理、代码示例、注意事项,以及相关问题是否合理。确保引用的内容被正确标注,比如引用[^2]对应Linux内核的应用,引用[^3]对应具体用法示例。 现在需要将这些内容组织成结构清晰的回答,符合用户要求的格式,使用中文,正确使用LaTeX格式,比如内联公式用$符号,独立公式用$$。不过当前问题可能不涉及数学公式,所以可能不需要使用数学表达式,但需要保持结构清晰,分点说明。 总结,回答的结构应该是: 1. 定义和作用 2. 实现原理(基于GCC内置函数) 3. 具体用法示例 4. 使用注意事项 5. 相关问题 确保每个部分都参考了提供的引用内容,并正确标注引用标识。同时,生成的代码示例需要用反引号包裹,正确显示语法高亮。</think>### likelyunlikely的含义及用法 #### 1. 定义与作用 `likely`和`unlikely`是编程中用于优化条件分支性能的宏。它们通过向编译器提供分支条件的概率信息,帮助编译器生成更高效的机器码。例如: - `likely(condition)`:表示`condition`为真的概率很大。 - `unlikely(condition)`:表示`condition`为假的概率很大。 #### 2. 实现原理 这两个宏基于GCC的`__builtin_expect`内置函数实现,语法为: ```c #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) ``` - `__builtin_expect(EXP, N)`:提示编译器`EXP == N`的概率很高[^1]。 - `!!(x)`:将任意表达式转换为布尔值(0或1),确保逻辑一致性。 #### 3. 代码示例 **场景1**:处理常见路径(使用`likely`) ```c if (likely(data_valid)) { // 数据有效时的高频操作 } else { // 错误处理的低频路径 } ``` **场景2**:处理罕见错误(使用`unlikely`) ```c if (unlikely(file_open_failed)) { // 文件打开失败的异常处理 } else { // 正常文件操作 } ``` #### 4. 注意事项 1. **适用场景**:明确分支概率时使用(如错误处理、核心循环)。 2. **编译器依赖**:主要针对GCC/Clang,其他编译器需适配(如MSVC的`__assume`)。 3. **避免滥用**:错误预测可能降低CPU流水线效率。 4. **内核应用**:广泛用于Linux内核(如网络栈、文件系统)[^2]。 #### 5. 性能优化机制 编译器会根据提示调整: - **代码布局**:高频路径紧邻无跳转指令,减少流水线冲刷。 - **分支预测**:生成`cmov`等无分支指令(概率极高时)。 - 汇编对比示例: ```asm ; 使用likely时 test %eax, %eax jne .Lhigh_prob_path ; 预测不跳转 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值