如果你翻过Linux内核源码,大概率见过这个模式:在if条件里塞一个unlikely()宏,把分支包起来。看起来只是多包了一层,但这个东西对性能的影响,可能远超你的想象。

简单说,unlikely是一个宏,定义长这样:

打开网易新闻 查看精彩图片
#define unlikely(e) __builtin_expect(!!(e), 0)

它把表达式e传给编译器内置函数__builtin_expect,并告诉编译器:这个条件大概率是假的(值为0)。反过来,likely宏就是告诉编译器条件大概率成立。

这本质上是在给CPU的分支预测器递小抄——提前告诉它哪条路更常走,让它提前把指令流水线填满,而不是等结果出来再后悔。

一个实验看清差异

为了验证这个宏到底有多大作用,有人写了两段几乎一模一样的循环,唯一的区别是一个用unlikely,一个用likely

// 用 unlikely 的版本if (unlikely(i % 1000 == 0)) {total += 2;} else {total += i + 1;// 用 likely 的版本if (likely(i % 1000 == 0)) {total += 2;} else {total += i + 1;}

在这个例子里,if分支只有0.1%的概率命中,else分支占了99.9%。也就是说,unlikely版本给编译器的是正确提示,likely版本给的提示跟实际执行路径完全相反。

把循环里的1000改成不同数值,观察不同命中率下的表现,结果很直观:当if分支命中率越来越低时,likely版本的性能越来越差。因为你告诉编译器要优化第一条分支,但实际执行几乎总是走第二条,分支预测器被带偏了,性能自然就崩了。

性能差异藏在汇编里

为什么一个提示能影响性能?答案在生成的汇编代码里。用gcc -O2编译后对比两个版本的汇编,可以看到编译器根据提示调整了分支布局——它会把更可能执行的代码放在更靠近的位置,减少跳转开销。

这个实验清楚地说明了一件事:unlikelylikely不是玄学,它们是在给编译器提供真实的运行时概率信息。用对了,性能有实实在在的提升;用反了,反而会拖慢程序。

下次写代码时,如果某个分支确实很少走到,不妨试试unlikely。但前提是——你的判断得靠谱,别把高频路径标成"不可能",那就得不偿失了。