提示断言中的分支预测

时间:2014-05-28 11:28:02

标签: c++ optimization branch-prediction

我有一个自定义ASSERT(...)宏,我在C ++应用程序中使用它。

#include <stdlib.h>
#include <iostream>

/// ASSERT(expr) checks if expr is true.  If not, error details are logged
/// and the process is exited with a non-zero code.
#ifdef INCLUDE_ASSERTIONS
#define ASSERT(expr)                                                      \
    if (!(expr)) {                                                        \
        char buf[4096];                                                   \
        snprintf (buf, 4096, "Assertion failed in \"%s\", line %d\n%s\n", \
                 __FILE__, __LINE__, #expr);                              \
        std::cerr << buf;                                                 \
        ::abort();                                                        \
    }                                                                     \
    else // This 'else' exists to catch the user's following semicolon
#else
#define ASSERT(expr)
#endif

最近我正在阅读一些Linux内核模块代码,并且遇到了likely(...)unlikely(...)宏的存在。这些提示给CPU提示给定分支更有可能,并且管道应该针对该路径进行优化。

根据定义,断言应该评估为真(即likely)。

我可以在ASSERT宏中提供类似的提示吗?这里的潜在机制是什么?

显然我会衡量表现的任何差异,但理论上它应该有所不同吗?

我只在Linux上运行我的代码,但是有兴趣知道是否有跨平台的方式来做这件事。我也在使用gcc,但也想支持clang。

3 个答案:

答案 0 :(得分:14)

性能增益可能不大,但这就是定义这些linux内核宏的方法:

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

因此,您可以像这样修改您的条件(假设expr预期为真,因此预期!(expr)为假):

if (__builtin_expect(!(expr), 0)) {

或者您可以定义与内核相同的宏,并使用它们以提高可读性。

这是gcc内置的,当然不可移植。

This表明clang也支持内置。不过,您可以使用上面的宏并在不支持内置的编译器上有条件地定义它们,如#define likely(x) (x)

在你的情况下,预测会很好(或者你或者你正在中止),所以不应该有悲观的风险,但如果你考虑更广泛地使用内置,这里有来自gcc文档的建议:

  

一般情况下,您应该更喜欢使用实际的配置文件反馈(-fprofile-arcs),因为程序员在预测程序实际执行情况方面非常糟糕。

答案 1 :(得分:10)

对于许多CPU,likelyunlikely(或其他任何事情)不向CPU提供分支提示(仅限于编译器,可能使用它来进行不同的优化,类似于配置文件引导优化),原因很简单,没有办法做到这一点。

例如,自P4起为x86定义了分支提示。在此之前他们没有任何影响,但更糟糕的是,他们对P4以外的任何事情都没有影响。所以它们没用(但浪费空间和带宽),据我所知,GCC不发射它们。

ARM不(但是?)也有分支提示。 PPC,IA64和SPARC确实有暗示分支,我不知道GCC是否会使用likelyunlikely,但至少它可以。

答案 2 :(得分:2)

无需任何其他注释。编译器已经很少知道abort被称为非常(每个程序执行最多一次),因此编译器会将包含abort的分支视为不可能的分支。您可以通过查看abort的声明来验证这一点。在glibc中,它被声明为

extern void abort (void) __THROW __attribute__ ((__noreturn__));

并在Visual Studio 2013中:

_CRTIMP __declspec(noreturn) void __cdecl abort(void);