Do branch likelihood hints carry through function calls?
gcc 10.2 at least is able to make this deduction (with -O2
).
If we consider the following simple program:
void foo();
void bar();
void baz(int x) {
if (x == 0)
foo();
else
bar();
}
then it compiles to:
baz(int):
test edi, edi
jne .L2
jmp foo()
.L2:
jmp bar()
However if we add [[likely]]
on the else
clause, the generated code changes to
baz(int):
test edi, edi
je .L4
jmp bar()
.L4:
jmp foo()
so that the not-taken case of the conditional branch corresponds to the "likely" case.
Now if we pull the comparison out into an inline function:
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();
}
we are again back to the original generated code, taking the branch in the bar()
case. But if we add [[likely]]
on the else
clause in is_zero
, we see the branch reversed again.
clang 10.0.1 however does not demonstrate this behavior and seems to ignore [[likely]]
altogether in all versions of this example.
Yes, it will probably inline, but this is quite pointless.
The __builtin_expect
will continue to work even after you upgrade to a compiler that supports those C++ 20 attributes. You can refactor them later, but it will be for purely aesthetic reasons.
Also, your implementation of the LIKELY
macro is erroneous (it is actually UNLIKELY
), the correct implementations are nelow.
#define LIKELY( x ) __builtin_expect( !! ( x ), 1 )
#define UNLIKELY( x ) __builtin_expect( !! ( x ), 0 )
The story appears to be mixed for different compilers.
On GCC, I think your inline likely
function works, or at least has some effect. Using Compiler Explorer to test differences on this code:
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;
}
}
This function f
adds 1 to x
and returns it, unless x
is 0, in which case it returns -3548. The LIKELY macro, when it's active, indicates to the compiler that the case where x
is zero is more common.
This version, with no change, produces this assembly under GCC 10 -O1:
f(int):
test edi, edi
je .L3
lea eax, [rdi+1]
ret
.L3:
mov eax, -3548
ret
With the #define
changed to the inline function with the [[likely]]
, we get:
f(int):
lea eax, [rdi+1]
test edi, edi
mov edx, -3548
cmove eax, edx
ret
That's a conditional move instead of a conditional jump. A win, I guess, albeit for a simple example.
This indicates that branch weights propagate through inline functions, which makes sense.
On clang, however, there is limited support for the likely and unlikely attributes, and where there is it does not seem to propagate through inline function calls, according to @Peter Cordes 's report.
There is, however, a hacky macro solution that I think also works:
#define EMPTY()
#define LIKELY(x) x) [[likely]] EMPTY(
Then anything like
if ( LIKELY(x) ) {
becomes like
if ( x) [[likely]] EMPTY( ) {
which then becomes
if ( x) [[likely]] {
.
Example: https://godbolt.org/z/nhfehn
Note however that this probably only works in if-statements, or in other cases that the LIKELY is enclosed in parentheses.