分支似然性提示是否通过函数调用进行?

问题描述

我遇到过几种情况,我想说一个函数的返回值可能在函数的主体内,而不是会调用它的if语句。

例如,说我想将代码从使用LIKELY宏移植到使用新的[[likely]]注释。但是这些在语法上却不同:

#define LIKELY(...) __builtin_expect(!!(__VA_ARGS__),0)
if(LIKELY(x)) { ... } 

vs

if(x) [[likely]] { ... }

没有简单的方法来重新定义LIKELY宏以使用注释。会定义类似

函数
inline bool likely(bool x) { 
  if(x) [[likely]] return true;
  else return false;
}

提示传播给if?喜欢

if(likely(x)) { ... }

类似地,在通用代码中,即使在其他地方是已知的,也可能很难在实际的if语句中直接表达算法似然信息。例如,copy_if的谓词几乎总是假的。据我所知,还没有办法使用属性来表达这一点,但是如果分支权重信息可以通过函数传播,这就是一个解决的问题。

到目前为止,我还没有找到有关此文档的文档,而且我不知道通过查看输出的程序集来测试它的良好设置。

解决方法

对于不同的编译器来说,故事似乎是混杂的。

在GCC上,我认为您的内联likely函数有效,或至少具有一定作用。使用编译器资源管理器测试此代码之间的差异:

inline bool likely(bool x) { 
  if(x) [[likely]] return true;
  else return false;
}

//#define LIKELY(x) likely(x)
#define LIKELY(x) x

int f(int x) {
    if (LIKELY(!x)) {
        return -3548;
    }
    else {
        return x + 1;
    }
}

此函数f将1加到x上并返回它,除非x为0,在这种情况下它将返回-3548。当LIKELY宏处于活动状态时,它向编译器指示x为零的情况更为常见。

此版本无更改,将在GCC 10 -O1下生成此程序集:

f(int):
        test    edi,edi
        je      .L3
        lea     eax,[rdi+1]
        ret
.L3:
        mov     eax,-3548
        ret

#define更改为[[likely]]的内联函数,我们得到:

f(int):
        lea     eax,[rdi+1]
        test    edi,edi
        mov     edx,-3548
        cmove   eax,edx
        ret

这是有条件的举动,而不是有条件的跳跃。我想这是一个胜利,尽管只是一个简单的例子。

这表明分支权重通过内联函数传播,这很有意义。

但是,根据@Peter Cordes的报告,在clang中,对可能和不太可能的属性的支持有限,而且似乎没有通过内联函数调用传播的地方。

但是,有一种我认为也可以使用的hacky宏解决方案:

#define EMPTY()
#define LIKELY(x) x) [[likely]] EMPTY(

然后类似

if ( LIKELY(x) ) {

变得像

if ( x) [[likely]] EMPTY( ) {

然后变成

if ( x) [[likely]] {

示例:https://godbolt.org/z/nhfehn

但是请注意,这可能仅在if语句中起作用,或者在其他情况下LIKELY用括号括起来。

,

gcc 10.2至少能够进行此推断(使用-O2)。

如果我们考虑以下简单程序:

void foo();
void bar();

void baz(int x) {
    if (x == 0)
        foo();
    else
        bar();
}

然后compiles to

baz(int):
        test    edi,edi
        jne     .L2
        jmp     foo()
.L2:
        jmp     bar()

但是,如果我们在[[likely]]子句上添加else,则生成的代码changes to

baz(int):
        test    edi,edi
        je      .L4
        jmp     bar()
.L4:
        jmp     foo()

以便条件分支的未采用情况对应于“可能”情况。

现在,如果我们将比较结果放入内联函数中

void foo();
void bar();

inline bool is_zero(int x) {
    if (x == 0)
        return true;
    else
        return false;
}

void baz(int x) {
    if (is_zero(x))
        foo();
    else
        bar();
}

我们再次back to the original generated code,在bar()情况下进入了分支。但是,如果我们在[[likely]]的{​​{1}}子句上添加else,则我们see the branch reversed again

clang 10.0.1并未表现出这种行为,并且在该示例的所有版本中似乎完全忽略了is_zero

,

是的,它可能会内联,但这毫无意义。

即使升级到支持那些C ++ 20属性的编译器,__builtin_expect仍将继续工作。您可以稍后对其进行重构,但这纯粹是出于美学原因。

此外,您对LIKELY宏的实现是错误的(实际上是UNLIKELY),正确的实现是必需的。

#define LIKELY( x )   __builtin_expect( !! ( x ),1 )
#define UNLIKELY( x ) __builtin_expect( !! ( x ),0 )

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...