我一直在深入研究Linux内核的一些部分,发现了这样的调用:
if (unlikely(fd < 0))
{
/* Do something */
}
or
if (likely(!err))
{
/* Do something */
}
我找到了它们的定义:
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
我知道它们是为了优化,但它们是如何工作的呢?使用它们可以减少多少性能/大小?至少在瓶颈代码中(当然是在用户空间中),这样做是否值得(可能还会失去可移植性)。
They cause the compiler to emit the appropriate branch hints where the hardware supports them. This usually just means twiddling a few bits in the instruction opcode, so code size will not change. The CPU will start fetching instructions from the predicted location, and flush the pipeline and start over if that turns out to be wrong when the branch is reached; in the case where the hint is correct, this will make the branch much faster - precisely how much faster will depend on the hardware; and how much this affects the performance of the code will depend on what proportion of the time hint is correct.
例如,在PowerPC CPU上,未提示的分支可能需要16个周期,正确提示的是8个周期,错误提示的是24个周期。在最内层循环中,良好的暗示可以产生巨大的差异。
可移植性并不是真正的问题——假设定义是在每个平台的头文件中;对于不支持静态分支提示的平台,可以简单地将“可能”和“不太可能”定义为零。
这些宏向编译器提供了关于分支可能走向的提示。如果这些宏可用,它们将扩展为特定于GCC的扩展。
GCC使用这些来优化分支预测。例如,如果您有以下内容
if (unlikely(x)) {
dosomething();
}
return x;
然后它可以重新构造这段代码,使其更像:
if (!x) {
return x;
}
dosomething();
return x;
这样做的好处是,当处理器第一次使用分支时,会有很大的开销,因为它可能已经预先加载和执行了代码。当它决定使用分支时,它必须使其无效,并从分支目标开始。
大多数现代处理器现在都有某种分支预测,但这只在您之前已经通过了分支,并且分支仍然在分支预测缓存中时才有帮助。
在这些场景中,编译器和处理器还可以使用许多其他策略。你可以在Wikipedia上找到关于分支预测器如何工作的更多细节:http://en.wikipedia.org/wiki/Branch_predictor
They cause the compiler to emit the appropriate branch hints where the hardware supports them. This usually just means twiddling a few bits in the instruction opcode, so code size will not change. The CPU will start fetching instructions from the predicted location, and flush the pipeline and start over if that turns out to be wrong when the branch is reached; in the case where the hint is correct, this will make the branch much faster - precisely how much faster will depend on the hardware; and how much this affects the performance of the code will depend on what proportion of the time hint is correct.
例如,在PowerPC CPU上,未提示的分支可能需要16个周期,正确提示的是8个周期,错误提示的是24个周期。在最内层循环中,良好的暗示可以产生巨大的差异。
可移植性并不是真正的问题——假设定义是在每个平台的头文件中;对于不支持静态分支提示的平台,可以简单地将“可能”和“不太可能”定义为零。
long __builtin_expect(long EXP, long C);
这个构造告诉编译器表达式EXP . xml
最有可能的值是c。返回值是EXP。
__builtin_expect用于条件句
表达式。在几乎所有的情况下,它将被用于
上下文的布尔表达式在这种情况下,它是很多
定义两个helper宏更方便:
#define unlikely(expr) __builtin_expect(!!(expr), 0)
#define likely(expr) __builtin_expect(!!(expr), 1)
这些宏可以用于
if (likely(a > 1))
参考:https://www.akkadia.org/drepper/cpumemory.pdf