Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do branch likelihood hints carry through function calls?

I've come across a few scenarios where I want to say a function's return value is likely inside the body of a function, not the if statement that will call it.

For example, say I want to port code from using a LIKELY macro to using the new [[likely]] annotation. But these go in syntactically different places:

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

vs

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

There's no easy way to redefine the LIKELY macro to use the annotation. Would defining a function like

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

propagate the hint out to an if? Like in

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

Similarly, in generic code, it can be difficult to directly express algorithmic likelihood information in the actual if statement, even if this information is known elsewhere. For example, a copy_if where the predicate is almost always false. As far as I know, there is no way to express that using attributes, but if branch weight info can propagate through functions, this is a solved problem.

So far I haven't been able to find documentation about this and I don't know a good setup to test this by looking at the outputted assembly.

like image 784
Riley Avatar asked Oct 09 '20 20:10

Riley


People also ask

What is __ Builtin_expect in C?

You can use the __builtin_expect built-in function to indicate that an expression is likely to evaluate to a specified value. The compiler can use this knowledge to direct optimizations. This built-in function is portable with the GNU C/C++ __builtin_expect function.

What is the use of likely and unlikely macros in Linux kernel?

A look at the Linux kernel code will show many if conditions enclosed in likely and unlikely macros. These macros invoke compiler directives that give the compiler a hint on the code leg that should be optimized for performance.


3 Answers

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.

like image 56
Anonymous1847 Avatar answered Nov 09 '22 11:11

Anonymous1847


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.

like image 39
Nate Eldredge Avatar answered Nov 09 '22 10:11

Nate Eldredge


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 )
like image 3
KevinZ Avatar answered Nov 09 '22 11:11

KevinZ