15

我有一个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。

4

3 回答 3

15

性能提升不太可能显着,但这就是定义这些 linux 内核宏的方式:

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

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

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

或者您可以定义与内核相同的宏并使用它们以获得更好的可读性。

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

表明 clang 也支持内置函数。否则,您可以使用上述宏并有条件地定义它们,就像#define likely(x) (x)在不支持内置函数的编译器上一样。

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

一般来说,您应该更喜欢为此使用实际的配置文件反馈(-fprofile-arcs),因为程序员在预测他们的程序实际执行方式方面出了名的差。

于 2014-05-28T11:46:11.530 回答
11

对于许多 CPU,likely并且unlikely(或与此相关的任何其他内容)不向 CPU 提供分支提示(仅向编译器提供,它可能会使用它来进行不同的优化,类似于配置文件引导优化),原因很简单:没办法。

例如,从 P4 开始为 x86 定义了分支提示。在此之前它们没有任何作用,但更糟糕的是,它们对除了 P4 之外的任何东西都没有作用。所以它们没用(但浪费空间和带宽),据我所知,GCC 不会发出它们。

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

于 2014-05-28T12:15:25.017 回答
2

不需要任何额外的注释。编译器已经知道很少abort被调用(每个程序执行最多一次),因此编译器无论如何都会将包含的分支视为不太可能的分支。您可以通过查看. 在 glibc 中它被声明为abortabort

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

在 Visual Studio 2013 中:

_CRTIMP __declspec(noreturn) void __cdecl abort(void);
于 2014-06-03T12:39:45.823 回答