高效,极不可能的条件

efficient, extremely-unlikely conditionals?

本文关键字:条件 不可能 高效      更新时间:2023-10-16

我有一些性能关键型代码,在极不可能的情况下会崩溃和烧毁(例如,当中间double等于0.0时,当该双精度值为零的可能性不大于任何其他值时(。

当然,一种选择是吹口哨并忽略角落情况,并祈祷它永远不会出现。但如果可能的话,我想抓住它;另一方面,我不太热衷于在性能关键型代码中添加条件。添加表单检查有什么技巧吗

if(val == 0.0)
{
    // extremely unlikely code path
}

以一种将性能损失降至最低的方式?在某些编译器上,可以传递 CPU 分支预测提示,但我听说这些对现代处理器不再有任何影响?

可能 , 不太可能

在 Linux 内核中,它们被定义为

#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

__builtin_expect宏是使用分支预测的 GCC 特定宏;它们告诉处理器条件是否可能为真,以便处理器可以在分支的正确"侧"预取指令。

您应该将定义包装在 ifdef 中,以确保在其他编译器上进行编译:

#ifdef __GNUC__
#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)
#else
#define likely(x)       (x)
#define unlikely(x)     (x)
#endif

你可能想看看这个问题的答案。如果您有任何在Windows上运行的想法,则可能需要也看看这个。

几乎可以肯定的是,值得你花时间看看分支预测的解释在回答这个问题时。(请注意,该问题中观察到的行为不仅在编译器中很常见,而且在语言中也很常见,Java 和 C++ 中的示例对此进行了说明。

据我了解,问题在于

if (val == 0.0)
{
  // extremely unlikely code path
}

是分支预测器最初将假定条件为真,也就是说,它将开始执行您极不可能的第一次遇到这种情况时立即提供代码路径。分支机构的成本与其说是对val == 0.0的评估但是,当val == 0.0被证明是错误的时,退出不太可能的分支的成本是错误的。但是,在遇到几次分支语句之后,分支预测器将猜测条件为假,并执行将继续进行,就像您告诉编译器"false"分支一样可能采取的那个。

在这样的环境中运行,一般不需要优化分支,因为它将为您优化。您可以对分支进行的唯一优化是第一次优化当它被调用时(也许还有其他几次,取决于确切的内容分支预测算法查找(。如果您的应用程序对性能非常关键,以至于您无法承受该成本即使是运行函数的前一两次,您是否应该在C++中编程?

为了鼓励分支预测器第一次走正确的道路,但是,你可以写

if (val != 0.0)
{
  // stuff you want to do almost every time
}
else
{
  // extremely unlikely code path
}

这有两个优点:首先,它是便携式的,其次,它获得了极不可能的代码这样您就可以更轻松地查看代码通常应该做什么。缺点是它增加了另一个级别的缩进 // stuff you want to do almost every time .

我承认自己也使用过这种技术。

您需要知道是否有条件实际上会严重损害性能。这是我使用的方法。

我倾向于假设,如果时间关键型代码中存在性能问题,则应追查该问题的实际问题。在诊断证明之前,不要假设有条件的伤害。