likely/unlikely 宏需用!!(x)强制转为0/1并加__GNUC__保护,-O2以上才生效,否则无效;误用于调试构建、重复标记或内联函数将失效,真正起效需分支本身构成流水线瓶颈。

它不能自动提升性能,只在分支概率高度偏斜(>90% 或
什么时候该用 __builtin_expect 而不是 if
当你的性能分析工具(如 perf record -e branches,branch-misses)明确指出某个 if 语句的 branch-misses 占比异常高,且你**确定该条件在运行时长期稳定地偏向一侧**时,才考虑替换。典型场景包括:
- 指针判空:
ptr != nullptr在绝大多数调用中为真 - 错误码检查:
errno == ENOMEM在内存充足环境下极少触发 - 循环边界:
i 在非越界访问的主路径中几乎恒为真 - 协议字段校验:
header->version == 2在已部署系统中版本固定
不要用于随机数据、调试开关、用户输入校验等概率不可控的分支。
如何安全封装 likely/unlikely 宏
直接写 __builtin_expect(x, 1) 容易出错:参数类型不匹配、副作用表达式被重复求值、MSVC 下编译失败。必须用预处理器宏封装,并加编译器保护:
#ifdef __GNUC__ # define likely(x) __builtin_expect(!!(x), 1) # define unlikely(x) __builtin_expect(!!(x), 0) #else # define likely(x) (x) # define unlikely(x) (x) #endif
关键点:
-
!!(x)强制转为 0/1,避免传入-1或2导致提示失效 - 宏定义必须用
#ifdef __GNUC__,__clang__已兼容 GCC 内置函数,无需单独判断 - 不要在宏里展开带副作用的表达式,比如
likely(x++)会执行两次自增
为什么 -O2 是硬性门槛
__builtin_expect 不是运行时函数,它只在编译期影响指令布局。若未启用 -O2 或更高优化等级,GCC/Clang 会直接忽略该提示,生成的汇编与普通 if 完全一致。验证方法:
- 编译时加
-S -O2生成汇编,搜索目标if对应的跳转指令(如je/jne) - 对比加与不加
likely()时,主路径代码是否从跳转目标变为直行(fall-through) - 用
objdump -d观察函数内指令地址连续性变化
在 -O0 下加 likely 纯属心理安慰,无任何效果。
常见误用与隐蔽陷阱
最常踩的坑不是语法错误,而是对“概率”的误判和上下文错配:
- 在调试构建(
-O0)中测试性能差异,结果毫无意义 - 对同一变量反复标记:
if (unlikely(ptr == nullptr)) { ... } else if (unlikely(ptr->flag == 0)) { ... }—— 编译器无法同时优化两个“unlikely”路径 - 在内联函数中滥用,导致提示被传播到不相关的调用点
- 忽略架构差异:ARM64 的分支预测器对指令布局更敏感,x86-64 某些微架构下收益可能不明显
真正起效的前提,是那个分支本身已在 CPU 流水线中形成稳定瓶颈 —— 这一点,比怎么写 likely 更重要。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











