← Back to Index

C++中的 likely / unlikely:它到底优化了什么?

在做 C++ 性能优化时,likely / unlikely 是一个非常容易被提到、但也非常容易被误解的话题。我接触到很多程序员(包括一些非常资深的程序员)经常会被这两个关键字所误导,甚至夸大他本身的作用。

很多人第一次接触这两个宏时,都会自然地产生两个问题:

  1. likely 导致有什么用,到底改变了什么
  2. likely 能控制 CPU 的分支预测吗?
  3. unlikely 会把代码自动放到 .cold 段吗?

直觉上我们很容易把 likely / unlikely 理解成某种“性能开关”。事实上,它们更准确的定位应该是:向编译器提供分支冷热倾向的提示(hint),帮助编译器优化代码布局,而不是直接命令 CPU 如何预测。

本文就围绕这个问题展开,结合汇编和工程实践,聊清楚 likely / unlikely 到底优化了什么、什么时候值得用、以及什么时候不值得用。

1. likely / unlikely 的定义

在 GCC / Clang 体系下,常见的定义通常如下:

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

这里的核心是 __builtin_expect

它的含义不是“让 CPU 一定预测成功”,而是告诉编译器:

其中 !!(x) 只是把任意表达式规范化为布尔值,避免传入复杂类型后产生歧义。

在 C++20 之后,也可以写成标准属性的形式:

if (cond) [likely](likely.html)
{
    fastPath();
}
else [unlikely](unlikely.html)
{
    slowPath();
}

这两种写法的目标是一致的:告诉编译器哪个分支更“热”,哪个分支更“冷”。

2. 它到底影响了什么?

很多文章会简单地说一句:“likely 能优化分支预测。”
这句话不能说完全错,但并不精确。

更准确地说,likely / unlikely 更常见的影响有下面几类:

2.1 影响编译器的代码布局(basic block layout)

这是最常见、也是最直接的影响。

编译器在生成汇编时,会决定:

如果某个分支被标记为 likely,编译器通常更倾向于把它布置成“顺着执行流直接落下去”的那条路径;而把冷分支放到需要跳转的位置。

这样做的好处是:

所以,likely / unlikely 的核心价值,往往首先体现在代码布局上。

2.2 影响静态分支倾向,而不是替代动态预测

现代 CPU 的分支预测器非常复杂,主要依赖运行时历史行为进行动态预测。
这意味着,真正执行时分支能不能预测成功,更多取决于:

likely / unlikely 并不能越过 CPU 的硬件预测器,直接“命令”它以后必须按某个方向预测。

所以更合理的理解是:

2.3 在特定情况下帮助冷热代码拆分

有些同学会问:unlikely 会不会把代码放到 .cold 段?

答案是:有可能,但不是绝对。

是否真的被拆到冷代码区,通常还要看:

因此,不能简单理解为“写了 unlikely,代码就一定进 .cold”。更准确的说法是:unlikely 可能帮助编译器识别冷路径,但最终是否拆分,取决于编译器整体决策。

3. 一个直观例子

我们先看一个非常典型的例子:

int foo(int x)
{
    if (likely(x > 0))
    {
        return x + 1;
    }
    else
    {
        return slowPath(x);
    }
}

这里的意图很清楚:

这时编译器更可能把 return x + 1; 这条路径直接排在判断之后,把 slowPath(x) 放到跳转路径上。

这里有一段代码很适合配合观察汇编差异:

https://godbolt.org/z/GbqocoebY

当代码块使用likely的时候,编辑器会把最终生成的代码快放在离判断条件更近的地方, 使得他有更好的code prefetch.

如果你在 Godbolt 中对比开启和不开启 likely 的版本,通常能看到:

这也是一个非常值得建立的性能直觉:

important

我们看到的源码顺序,并不等于最终机器执行时的指令排布顺序。

4. 它真的能“控制分支预测”吗?

这是最核心的误区。

如果把“控制分支预测”理解成:

那么答案是否定的。

CPU 的动态分支预测器并不会因为你写了 likely(x),就放弃自己的历史统计和运行时判断。

但如果把“控制分支预测”理解成:

那么答案又是肯定的。

所以更准确的表达应该是:

likely / unlikely 影响的是编译器对分支的静态理解和代码布局,它可能间接帮助执行效率,但不是直接操控硬件分支预测器。

5. 什么时候值得使用?

我更推荐在系统的关键路径上来增加这种可能影响CPU性能的行为。CPU的预测在绝大多数的时候都能工作地很好,所以,如果你想增加likely/unlikely,请一定要有足够的性能测试数据来支撑你的代码!

总结

likely / unlikely 的真正价值,不在于“控制 CPU”,而在于帮助编译器更好地理解代码的冷热结构,并尽可能把热路径排布得更连续。

它适合用于:

但它并不适合滥用。
在现代 C++ 性能优化中,likely / unlikely 更像是一把小而精的手术刀,而不是一把万能锤子。

真正可靠的性能优化,依然应当建立在性能分析工具、业务路径统计和汇编验证的基础之上。