问题描述
我遇到过几种情况,我想说一个函数的返回值可能在函数的主体内,而不是会调用它的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 )