5

There are [[likely]] and [[unlikely]] attributes in modern C++. There are corresponding __builtin_expect(x, 1) and __builtin_expect(x, 0) builtins in G++ and clang++. But also there are __builtin_unpredictable(x) and __builtin_expect_with_probability(x, 1, 0.5) or (equally) __builtin_expect_with_probability(x, 0, 0.5) builtins, which tells compiler to prevent CPU to fill pipeline with instructions from (mis)predicted branch, because the cost of flush+recovery of the pipeline from mispredicted path is statistically greater than execution w/o speculative execution at all.

Would be the using of [[likely]] or equally [[unlikely]] attributes on both if and else branches like in following snippets an equivalent of the using of hypothetical [[unpredictable]] attribute?

if (x) [[likely]] {
    // "if" branch
} else [[likely]] {
    // "else" branch
}

or

if (x) [[unlikely]] {
    // "if" branch
} else [[unlikely]] {
    // "else" branch
}

As I know if branch is treated by compilers as [[likely]] by default if there is else, and [[unlikely]] if there no else (because it is often the form of unhappy path checking with early exit from the current function). So if I just omit either of attributes, then it is not equivalent to specify hypothetical [[unpredictable]] attribute.

Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169
  • unclear what you're asking – OrenIshShalom May 15 '22 at 07:43
  • @OrenIshShalom slightly edited (sorry for my bad English) – Tomilov Anatoliy May 15 '22 at 07:45
  • so you're asking whether `if [[ likely ]] + else [[ likely ]] == if [[ unpredictable ]]`? something like that? (`==` here obviously means "the same as") – OrenIshShalom May 15 '22 at 07:58
  • @OrenIshShalom yes, exactly. – Tomilov Anatoliy May 15 '22 at 08:07
  • 1
    Making `x` `volatile` could possibly have the same effect. You'd need to compare the assmbly code to see if that works (and doesn't have any other nasty effects) though. So, `if (static_cast>(x))` would be the idea. – Ted Lyngmo May 15 '22 at 08:34
  • Can you provide a [mre] where using `__builtin_unpredictable` results in different assembly code than without using it? It'd help when trying to come up with an alternative. – Ted Lyngmo May 15 '22 at 08:51
  • The `if` branch is not equal to `[[likely]]`. The compiler is free to arrange the assembly as it likes. It is possible that the else branch will be taken as likely by the compiler... For example the if branch is 100 asm instructions, but the else branch is 3. Then it is probably better to take the shorter branch as the likely. But this is just a guess. Compilers does pretty nasty things so you must check the assembly to see the effect. Btw marking a branch with likely puts that branch right after the comparison to avoid jumps in my experience. And then the branch predictior in the CPU comes in – simre May 15 '22 at 10:34
  • @TedLyngmo Can't make all the tests on x86 to go through the [branch](https://github.com/llvm/llvm-project/blob/96c2a0c9fff24803be14bfaa579e4f230763d3cc/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp#L608). It seems it is needed for Objective C++. – Tomilov Anatoliy May 27 '22 at 19:48

1 Answers1

1

It seems marking both if and else branch with likely gets a warning (unlikely the same):

main.cpp:9:27: warning: both branches of ‘if’ statement marked as ‘likely’ [-Wattributes]
    9 |             if (i*j == p) [[likely]]
      |                           ^~~~~~~~~~
   10 |                 return 0;
   11 |             else [[likely]]
      |                  ~~~~~~~~~~

So I guess the question remains hypothetical. As for a minimal complete example, it seems g++ supports [[likely]]and [[unlikely]] but not __builtin_unpredictable. Similarly, clang++ supports __builtin_unpredictable but not [[likely]] nor [[unlikely]] (at least clang 10 doesn't. So comparing all three options is tricky. Here is a minimal complete example that compares [[likely]] versus [[unlikely]]:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
bool isPrime(int p) {
    for (int i=2;i<p;i++)
        for (int j=2;j<p;j++)
            if (i*j == p) [[likely]]
                return 0;
            // else [[unlikely]] <--- gets a warning
            //     j++; <--- remove from for loop 
    return 1; }
int main(int argc, char **argv) {
    int numPrimes=0;
    int start=atoi(argv[1]);
    int end=atoi(argv[2]);
    for (int p=atoi(argv[1]);p<atoi(argv[2]);p++)
        if (isPrime(p)) { numPrimes++;}
    printf("There are %d primes between %d and %d",numPrimes,start,end);
}

And here is the evaluation:

$ g++ -std=c++2a -O3 main.cpp -o main
$ time ./main 2 10000
There are 1229 primes between 2 and 10000
real    1m16.083s
user    1m14.014s
sys 0m0.247s
$ sed -i "s/likely/unlikely/g" main.cpp
$ g++ -std=c++2a -O3 main.cpp -o main
$ time ./main 2 10000
There are 1229 primes between 2 and 10000
real    0m38.277s
user    0m38.100s
sys 0m0.012s
OrenIshShalom
  • 5,974
  • 9
  • 37
  • 87
  • Your benchmark only shows `[[likely]]` vs. `[[unlikely]]`, not what happens with neither. And BTW, clang supports `__builtin_expect` I think, so you can use the GNU C style `if (likely(condition))` with [an appropriate macro definition](https://stackoverflow.com/questions/109710/how-do-the-likely-unlikely-macros-in-the-linux-kernel-work-and-what-is-their-ben) to give the optimizer the same information a different way. Also, that's a pretty specific microbenchmark, but I guess there's still scope for making the asm jump over a `ret` vs. normally not-taken breaking out of the loop. – Peter Cordes May 15 '22 at 14:22