网易首页 > 网易号 > 正文 申请入驻

谷歌工程师花20年优化这行代码,C++20终于让它"退役"了

0
分享至

Linux内核里有超过10万行代码在用两个宏:LIKELYUNLIKELY。从2002年引入到现在,它们帮内核节省了无数CPU周期。但C++20发布4年后,真正全面切到[[likely]][[unlikely]]的项目,屈指可数。

分支预测失败一次要15-20个周期。在每秒执行数十亿次的循环里,猜错就是性能灾难。现代CPU靠猜测走哪条路来维持流水线运转,猜对了继续飞,猜错了全部推倒重来。我们这篇文章要解决的,就是怎么让编译器替CPU"押对宝"。

从宏到属性:一场持续20年的语法战争

GCC的__builtin_expect在2002年就出现了。它的用法反人类:第二个参数是"你期望的结果",0代表假,1代表真。写成__builtin_expect(ptr != NULL, 1)的意思是"我赌这个指针不为空"。

Linux内核的开发者很快受够了这种可读性灾难。他们封装了两个宏:

#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)

双感叹号是保险:把任何表达式强制转成布尔值,避免__builtin_expect的宏展开陷阱。这个设计被抄进了Chromium、WebKit、MySQL——几乎所有对性能敏感的C++代码库。

C++20的[[likely]][[unlikely]]本该终结这一切。语法干净,标准保证,跨平台兼容。但现实是:2024年的CMake项目里,你依然能看到三层条件编译——#if __cplusplus >= 202002L走属性,否则回退到宏,再否则用编译器内置。

为什么切不动?一个原因是代码库的编译器矩阵太宽。Android的NDK、游戏主机的封闭SDK、某些嵌入式工具链,C++20支持要么残缺要么不存在。另一个原因更微妙:[[likely]]的位置语法和__builtin_expect完全不同,大规模迁移需要人肉审计每一处逻辑,而不是简单的正则替换。

编译器到底怎么用这些提示

给分支加提示,本质是在干预代码布局。编译器会把[[likely]]路径放在"直达"位置,CPU取指时顺序读到,不用跳转。[[unlikely]]路径被挤到远处,触发跳转时才会加载。

这影响的不只是分支预测器,还有指令缓存(Instruction Cache)。热路径紧凑排列,缓存命中率更高。冷路径被物理隔离,不会污染缓存行。

GCC和Clang的处理有细微差别。GCC对[[likely]]的支持相对保守,高优化等级下可能完全忽略你的提示,转而依赖PGO(Profile-Guided Optimization)的实测数据。Clang更听话,但也会在PGO数据冲突时选择相信实测。

一个反直觉的事实:在PGO启用的构建里,手动加的[[likely]]可能起反作用。编译器已经知道真实的热点分布,你的"经验判断"反而成了噪声。Google的Abseil库文档里明确建议:PGO构建时考虑移除所有手动分支提示。

真实世界的使用模式

错误处理是最典型的[[unlikely]]场景。解析网络包时,格式错误是例外而非规则:

if (header.magic != PROTOCOL_MAGIC) [[unlikely]] {
return Error::Corrupt;
}

循环终止条件则是[[likely]]的重灾区。遍历链表时,"还没到头"是常态:

while (node != nullptr) [[likely]] {
process(node);
node = node->next;
}

但这里有个陷阱。[[likely]]在while上的语义,和直觉可能相反。它修饰的是"继续循环"这个分支,意味着你预测循环会执行很多次。如果实际平均只跑3次,提示就帮了倒忙。

switch语句的支持是C++20后期补上的。协议解析器可以这样写:

switch (packet.type) {
[[likely]] case PacketType::Data: handle_data(); break;
[[unlikely]] case PacketType::Error: handle_error(); break;
[[unlikely]] case PacketType::Control: handle_control(); break;
}

注意语法:属性要放在case标签前面,不是语句前面。这是很多人第一次用就编译报错的原因。

编译器标志:比属性更隐蔽的控制

除了源代码里的提示,编译器命令行也有分支预测的杠杆。GCC和Clang支持-fprofile-generate-fprofile-use,先跑一遍收集数据,再编译时利用真实的分支频率。

PGO的效果通常碾压手动提示。Facebook的HHVM团队曾公开数据:PGO带来的性能提升在15%-30%区间,而手动LIKELY/UNLIKELY只有3%-8%。但PGO的构建流程复杂,需要代表性工作负载,CI/CD集成成本高。

另一个少人知道的标志是-mbranch-cost=N,告诉编译器"分支有多贵"。默认值因架构而异,ARM上通常比x86低。调高这个值,编译器会更激进地用条件移动(CMOV)替代分支,哪怕增加指令数。

LLVM还有个实验性标志-mllvm -branch-prob-precise,改进静态分支概率的估算精度。默认启发式假设循环迭代很多次、错误路径很少走,这个标志让编译器更谨慎,减少误判。

什么时候你的提示会失效

编译器不是必须听你的。[[likely]][[unlikely]]在标准里的措辞是"建议"(recommended),不是"要求"。以下情况你的提示会被无视:

优化等级低于-O2时,GCC直接忽略所有分支属性。代码规模优化(-Os)模式下,编译器可能为了压缩体积而牺牲你的热点布局。链接时优化(LTO)阶段,跨函数的内联可能打乱你精心设计的分支结构。

更隐蔽的是CPU层面的演化。Intel从Sandy Bridge开始,分支预测器已经能学习循环的迭代次数模式。简单的"继续循环"提示,对现代硬件的价值在递减。Apple的M系列芯片走得更远,其分支目标缓冲区(BTB)容量大到能缓存数万条分支历史,静态提示的边际收益被进一步压缩。

但别急着扔掉这些工具。在极端场景下——中断处理程序、实时系统的确定性路径、解析器的状态机跳转——你仍然需要明确的控制。只是要意识到,性能优化的战场已经从"提示编译器"部分转移到了"配合编译器的自动分析"。

迁移策略:从宏到属性的渐进路线

如果你维护着大量使用LIKELY/UNLIKELY的代码,直接全局替换是自杀。推荐的做法是封装一个抽象层:

#if __cplusplus >= 202002L
#define LIKELY [[likely]]
#define UNLIKELY [[unlikely]]
#else
#define LIKELY
#define UNLIKELY
#endif

等等,这不对。[[likely]]的位置语法和宏展开不兼容,不能直接替换。正确的做法是定义成属性宏,或者更激进一点——用C++20的if constexpr重构关键路径,彻底放弃运行时分支。

Google的Chromium项目选择了另一条路:保留LIKELY/UNLIKELY宏,但在C++20模式下内部展开成[[likely]][[unlikely]]。这需要宏的魔法,因为属性在C++里不是记号(token),而是语法构造。他们的解决方案是用__attribute__((likely))作为桥梁,GCC和Clang都支持这种形式,且位置语义与C++20属性一致。

一个血泪教训:不要在条件表达式里嵌套[[likely]]。写成if (a && b) [[likely]]是合法的,但if (a [[likely]] && b)会编译失败。属性的附着点规则比看起来要严格得多。

" Type="normal"@@-->

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
曼城花1.2亿买的后腰,评分比替补门将还低3分

曼城花1.2亿买的后腰,评分比替补门将还低3分

热血体育社
2026-04-13 16:27:58
美股期指全线跳水,芯片股盘前下挫,金银下跌,霍尔木兹海峡又有新消息

美股期指全线跳水,芯片股盘前下挫,金银下跌,霍尔木兹海峡又有新消息

台州交通广播
2026-04-13 20:05:40
4月26日起,北京又一大型商场暂停营业!

4月26日起,北京又一大型商场暂停营业!

美丽大北京
2026-04-12 21:54:29
比土木更难就业的专业出现了:全班同学都没签三方,辅导员被气晕

比土木更难就业的专业出现了:全班同学都没签三方,辅导员被气晕

黯泉
2026-04-11 15:57:13
抢走王朔,睡遍京圈,定居国外多年的“坏种”徐静蕾,成最终赢家

抢走王朔,睡遍京圈,定居国外多年的“坏种”徐静蕾,成最终赢家

橙星文娱
2026-04-13 11:46:44
掀掉洋葱顶,整治宗教泛滥的第一步

掀掉洋葱顶,整治宗教泛滥的第一步

黑哥讲现代史
2026-03-14 15:46:38
38岁老板娘沦为陪睡工具:揭秘黑茶高端骗局,入局者10有9个离婚

38岁老板娘沦为陪睡工具:揭秘黑茶高端骗局,入局者10有9个离婚

云景侃记
2026-02-12 22:21:30
国内套现7个亿后,她露出了身后的美国国旗,整个家族共套现20亿

国内套现7个亿后,她露出了身后的美国国旗,整个家族共套现20亿

阅微札记
2026-04-13 19:43:44
三观炸裂!翟欣欣出轨聊天记录流出,尺度大到咂舌,判12年都嫌少

三观炸裂!翟欣欣出轨聊天记录流出,尺度大到咂舌,判12年都嫌少

有范又有料
2025-09-29 14:21:11
第四届烂柯杯首轮七盘中韩战 王世一VS卞相壹马靖原挑战申真谞

第四届烂柯杯首轮七盘中韩战 王世一VS卞相壹马靖原挑战申真谞

劲爆体坛
2026-04-13 19:13:47
又一起吃他汀猝死!医生再三提醒:春季吃他汀的人,要警惕这4点

又一起吃他汀猝死!医生再三提醒:春季吃他汀的人,要警惕这4点

岐黄传人孙大夫
2026-04-13 14:10:03
当年一个师长一个排长,28 年后师长成为上将,排长却成为了大将

当年一个师长一个排长,28 年后师长成为上将,排长却成为了大将

云霄纪史观
2026-04-12 18:11:35
包养情人无数,娶初中同学女儿为妻,玩老婆闺蜜,孙道存有多放荡

包养情人无数,娶初中同学女儿为妻,玩老婆闺蜜,孙道存有多放荡

云景侃记
2026-03-15 22:05:44
上午10点!北京国安做出重要决定:申诉廖力生手球+韦世豪逃红牌

上午10点!北京国安做出重要决定:申诉廖力生手球+韦世豪逃红牌

球场新视角1号
2026-04-13 10:48:49
三位中国象棋特级大师遭终身禁赛重罚,世界冠军蒋川被禁赛五年

三位中国象棋特级大师遭终身禁赛重罚,世界冠军蒋川被禁赛五年

米修体育
2026-04-13 13:57:57
美媒预测:第三次世界大战可能在5个地点爆发,2个在中国家门口

美媒预测:第三次世界大战可能在5个地点爆发,2个在中国家门口

说宇宙
2026-04-11 09:15:03
2000吨英国潜艇消失,我国暗中打捞拆解研究,39年后首相却上门讨要

2000吨英国潜艇消失,我国暗中打捞拆解研究,39年后首相却上门讨要

睡前讲故事
2026-03-30 13:48:58
中国女篮官宣19人集训名单:韩旭李月汝领衔 张子宇杨舒予在列

中国女篮官宣19人集训名单:韩旭李月汝领衔 张子宇杨舒予在列

醉卧浮生
2026-04-13 19:16:50
特朗普威胁若中国为伊朗提供军事装备将加征50%关税,外交部回应

特朗普威胁若中国为伊朗提供军事装备将加征50%关税,外交部回应

极目新闻
2026-04-13 15:50:01
卫星化学股价再创新高

卫星化学股价再创新高

每日经济新闻
2026-04-13 13:34:26
2026-04-13 21:08:49
像素与芯片
像素与芯片
有态度网友ytd
1649文章数 9关注度
往期回顾 全部

科技要闻

"抄作业"近四年,马斯克版微信周五上线

头条要闻

媒体:欧尔班败选不仅是一国之事 牵扯到与中国的关系

头条要闻

媒体:欧尔班败选不仅是一国之事 牵扯到与中国的关系

体育要闻

一支球队不够烂,也是一种悲哀

娱乐要闻

初代“跑男团”合体,邓超、鹿晗缺席

财经要闻

今夜,出大事了,3种结果

汽车要闻

不止命名更纯粹 领克10/10+要做纯电操控新王

态度原创

数码
艺术
游戏
旅游
房产

数码要闻

前行者推32kHz回报率磁轴键盘ES68破晓,预售到手低至699元

艺术要闻

22位中国当代名家油画作品

EWC电竞世界杯: LPL的仇,EWC报!JDG三局击败BLG

旅游要闻

免费、出片、治愈系!这片月见草花海才是春日顶配

房产要闻

6000亿投资盛宴,全球巨头齐聚,海南又要干件大事!

无障碍浏览 进入关怀版