__builtin_expect 不会改变程序行为或提升单次分支速度,仅在 -O2+、GCC/Clang 下对长期稳定偏斜(>95%)的热点分支有效;需用 !!(x) 转布尔、统一头文件定义宏、避免副作用,并通过汇编与 perf 验证生效。

加 __builtin_expect 不会改变程序行为,也不会让单次 if 变快;它只在 -O2 或更高优化等级、GCC/Clang 下,对概率长期稳定偏斜(>95% 或
哪些 if 真该加 likely/unlikely?
不是所有分支都值得干预。真正起效的前提是:已被 perf record -e branches,branch-misses 定位为瓶颈,且运行时概率长期高度偏斜。
-
ptr != nullptr—— 已知 99%+ 调用中指针非空,且该检查在 hot 函数入口或循环内高频执行 -
ret == -1 && errno == ENOMEM—— 内存充足环境下错误极少触发,但检查语句紧贴系统调用后 i —— 在 for 循环头部反复出现,<code>i绝大多数迭代都在合法范围内-
header->version == 2—— 协议已全量升级,旧版本字段几乎不出现 - 别碰日志开关、配置项判断、用户输入校验、调试路径里的 if —— 概率不可控,编译器静态分析后直接忽略你的提示
likely/unlikely 宏必须这么写才安全
直接调用 __builtin_expect(ptr != nullptr, 1) 极易出错:指针非空时值可能是 0x7fffabcd,传给第二个参数的其实是这个地址值而非 1,提示完全失效。
- 必须用双叹号:
!!(x)强制转成0或1,否则任意非零整数/指针地址都会破坏提示语义 - 只靠
#ifdef __GNUC__判断,不要单独加#ifdef __clang__—— Clang 兼容 GCC 内置函数,但宏定义行为不一致可能引发隐晦问题 - 宏必须放在公共头文件里统一包含,禁止在多个
.cpp中重复定义 - 严禁在宏展开式里带副作用,例如
likely(i++)会导致i被求值两次
为什么加了却没提速?先查这三件事
“加了等于没加”几乎从不因为语法错误,而是上下文不匹配。
- 没开
-O2或更高 ——__builtin_expect在-O0或-O1下被编译器完全忽略,生成汇编和普通 if 一模一样 - 分支实际概率不偏斜 —— 用
perf stat -e branches,branch-misses ./a.out看branch-misses占比,低于 5% 的分支加了也白加 - 函数未内联 —— 提示只作用于当前函数层级;若分支藏在被调函数里,外层加
likely没用,得先确认该函数是否被内联(看汇编或加[[gnu::always_inline]])
怎么验证它真的生效了?
别信编译通过,也别只看耗时波动。最可靠的方式是两级验证:
- 用
g++ -S -O2 main.cpp生成汇编,搜索目标函数,确认主路径是否变成直行(fall-through),错误处理是否跳转到函数末尾附近 - 用
objdump -d a.out | grep -A10 "your_func_name"观察指令地址是否更连续;再跑perf stat -e branches,branch-misses ./a.out对比前后数值,下降 ≥5% 才算有效果
真正难的从来不是写对宏,而是准确识别哪个分支既高频、又足够偏斜、还恰好构成流水线瓶颈——这需要你亲手用 perf 去看,而不是凭感觉猜。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











