我一直在研究Linux内核的某些部分,并找到了如下调用:
if (unlikely(fd < 0))
{
/* Do something */
}
要么
if (likely(!err))
{
/* Do something */
}
我找到了它们的定义:
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
我知道它们是为了优化,但是它们如何工作? 使用它们会导致多少性能/尺寸下降? 至少在瓶颈代码中(当然在用户空间中)值得麻烦(并且可能会失去可移植性)。
#1楼
它们是对编译器的提示,以在分支上生成提示前缀。 在x86 / x64上,它们占用一个字节,因此每个分支最多最多增加一个字节。 至于性能,它完全取决于应用程序-如今,在大多数情况下,处理器上的分支预测器将忽略它们。
编辑:忘记了他们实际上可以真正提供帮助的地方。 它可以允许编译器对控制流图进行重新排序,以减少为“可能”路径采取的分支数量。 在检查多个退出案例的循环中,这可以显着改善。
#2楼
这些是GCC函数,供程序员使用,以向编译器提示有关给定表达式中最有可能发生分支条件的情况。 这使编译器可以构建分支指令,以便最常见的情况是执行最少数量的指令。
分支指令的构建方式取决于处理器体系结构。
#3楼
它们提示编译器发出指令,这些指令将导致分支预测偏向跳转指令的“可能”一侧。 如果预测正确,这将是一个巨大的胜利,这意味着跳转指令基本上是免费的,将花费零个周期。 另一方面,如果预测错误,则意味着需要清除处理器管道,这可能会花费多个周期。 只要预测在大多数时间都是正确的,这将对性能有好处。
像所有这样的性能优化一样,您应该仅在进行广泛的性能分析后才能执行此操作,以确保代码确实处于瓶颈,并且可能考虑到微观性质,因此代码正在紧密循环中运行。 通常,Linux开发人员都非常有经验,所以我想他们会做到的。 他们并不十分在意可移植性,因为它们只针对gcc,并且对要生成的程序集有着非常密切的了解。
#4楼
它们使编译器在硬件支持它们的地方发出适当的分支提示。 这通常仅意味着在指令操作码中旋转几位,因此代码大小不会改变。 CPU将开始从预测的位置获取指令,并刷新管道并在到达分支后发现错误的情况下重新开始;否则,CPU将重新启动管道。 在提示正确的情况下,这将使分支速度更快-确切的速度取决于硬件; 以及这对代码性能的影响将取决于时间提示的正确比例。
例如,在PowerPC CPU上,无提示的分支可能需要16个周期,正确提示的分支可能为8,而错误提示的分支可能为24。在最内层的循环中,良好的提示可能会产生巨大的影响。
可移植性并不是真正的问题-大概定义在每个平台的头文件中。 对于不支持静态分支提示的平台,您可以简单地将“可能”和“不太可能”定义为空。
#5楼
这些是宏,可向编译器提示分支可能的运行方式。 如果可用,宏会扩展为GCC特定的扩展。
GCC使用这些来优化分支预测。 例如,如果您有以下内容
if (unlikely(x)) {
dosomething();
}
return x;
然后,可以将这段代码重组为类似以下内容的代码:
if (!x) {
return x;
}
dosomething();
return x;
这样做的好处是,当处理器第一次执行分支时,会产生很大的开销,因为它可能是在推测性地加载和执行代码。 当确定要转到分支时,则必须使其无效,并从分支目标开始。
现在,大多数现代处理器都具有某种分支预测功能,但这仅在您之前浏览过分支并且分支仍位于分支预测缓存中时才有帮助。
在这些情况下,编译器和处理器可以使用许多其他策略。 您可以在Wikipedia上找到有关分支预测变量如何工作的更多详细信息: http : //en.wikipedia.org/wiki/Branch_predictor