I came across a #define
in which they use __builtin_expect
.
The documentation says:
Built-in Function:
long __builtin_expect (long exp, long c)
You may use
__builtin_expect
to provide the compiler with branch prediction information. In general, you should prefer to use actual profile feedback for this (-fprofile-arcs
), as programmers are notoriously bad at predicting how their programs actually perform. However, there are applications in which this data is hard to collect.The return value is the value of
exp
, which should be an integral expression. The semantics of the built-in are that it is expected thatexp == c
. For example:if (__builtin_expect (x, 0)) foo ();
would indicate that we do not expect to call
foo
, since we expectx
to be zero.
So why not directly use:
if (x)
foo ();
instead of the complicated syntax with __builtin_expect
?
Well, as it says in the description, the first version adds a predictive element to the construction, telling the compiler that the
x == 0
branch is the more likely one - that is, it's the branch that will be taken more often by your program.With that in mind, the compiler can optimize the conditional so that it requires the least amount of work when the expected condition holds, at the expense of maybe having to do more work in case of the unexpected condition.
Take a look at how conditionals are implemented during the compilation phase, and also in the resulting assembly, to see how one branch may be less work than the other.
However, I would only expect this optimization to have noticeable effect if the conditional in question is part of a tight inner loop that gets called a lot, since the difference in the resulting code is relatively small. And if you optimize it the wrong way round, you may well decrease your performance.
Let's decompile to see what GCC 4.8 does with it
Blagovest mentioned branch inversion to improve the pipeline, but do current compilers really do it? Let's find out!
Without
__builtin_expect
Compile and decompile with GCC 4.8.2 x86_64 Linux:
Output:
The instruction order in memory was unchanged: first the
puts
and thenretq
return.With
__builtin_expect
Now replace
if (i)
with:and we get:
The
puts
was moved to the very end of the function, theretq
return!The new code is basically the same as:
This optimization was not done with
-O0
.But good luck on writing an example that runs faster with
__builtin_expect
than without, CPUs are really smart those days. My naive attempts are here.C++20
[[likely]]
and[[unlikely]]
C++20 has standardized those C++ built-ins: How to use C++20's likely/unlikely attribute in if-else statement They will likely (a pun!) do the same thing.
I don't see any of the answers addressing the question that I think you were asking, paraphrased:
The title of your question made me think of doing it this way:
If the compiler assumes that 'true' is more likely, it could optimize for not calling
foo()
.The problem here is just that you don't, in general, know what the compiler will assume -- so any code that uses this kind of technique would need to be carefully measured (and possibly monitored over time if the context changes).
The idea of
__builtin_expect
is to tell the compiler that you'll usually find that the expression evaluates to c, so that the compiler can optimize for that case.I'd guess that someone thought they were being clever and that they were speeding things up by doing this.
Unfortunately, unless the situation is very well understood (it's likely that they have done no such thing), it may well have made things worse. The documentation even says:
In general, you shouldn't be using
__builtin_expect
unless:Imagine the assembly code that would be generated from:
I guess it should be something like:
You can see that the instructions are arranged in such an order that the
bar
case precedes thefoo
case (as opposed to the C code). This can utilise the CPU pipeline better, since a jump thrashes the already fetched instructions.Before the jump is executed, the instructions below it (the
bar
case) are pushed to the pipeline. Since thefoo
case is unlikely, jumping too is unlikely, hence thrashing the pipeline is unlikely.I test it on Mac according @Blagovest Buyukliev and @Ciro. The assembles look clear and I add comments;
Commands are
gcc -c -O3 -std=gnu11 testOpt.c; otool -tVI testOpt.o
When I use -O3 , it looks the same no matter the __builtin_expect(i, 0) exist or not.
When compile with -O2 , it looks different with and without __builtin_expect(i, 0)
First without
Now with __builtin_expect(i, 0)
To summarize, __builtin_expect works in the last case.