15

I have a custom ASSERT(...) macro which I use in a C++ application.

#include <stdlib.h>
#include <iostream>

/// ASSERT(expr) checks if expr is true.  If not, error details are logged
/// and the process is exited with a non-zero code.
#ifdef INCLUDE_ASSERTIONS
#define ASSERT(expr)                                                      \
    if (!(expr)) {                                                        \
        char buf[4096];                                                   \
        snprintf (buf, 4096, "Assertion failed in \"%s\", line %d\n%s\n", \
                 __FILE__, __LINE__, #expr);                              \
        std::cerr << buf;                                                 \
        ::abort();                                                        \
    }                                                                     \
    else // This 'else' exists to catch the user's following semicolon
#else
#define ASSERT(expr)
#endif

Recently I was reading some Linux kernel module code and came across the existence of likely(...) and unlikely(...) macros. These provide a hint to the CPU that a given branch is more likely, and that the pipeline should optimise for that path.

Assertions are, by definition, expected to evaluate to true (i.e. likely).

Can I provide a similar hint in my ASSERT macro? What's the underlying mechanism here?

Obviously I will measure for any difference in performance, but in theory should it make any difference?

I only run my code on Linux, but would be interested to know if there's a cross platform way of doing this too. I'm also using gcc, but would like to support clang as well.

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • 1
    Anything wrong with assert from ``? – Mat May 28 '14 at 11:31
  • 1
    Unless you're going to be putting `ASSERT`s inside performance-critical loops then it really isn't going to make any difference. Also branch prediction is pretty good for consistent branches such as this these days, so even in a performance-critical loop it shouldn't make much difference on a modern CPU. – Paul R May 28 '14 at 11:31
  • @Mat, the rationale was primarily to allow including asserts in `Release` and `RelWithDebInfo` builds. It's controlled via `INCLUDE_ASSERTIONS` which is independent of the build type. – Drew Noakes May 28 '14 at 11:33
  • 1
    Unless your assertions are on a hot path I doubt you'll notice any difference, and even then I doubt the difference will be significant. Also, what is your question exactly? `Can I provide a similar hint in my ASSERT macro?` Yes, you can of course use `likely` and `unlikely` if you so wish. – user703016 May 28 '14 at 11:43
  • Side note: you don't need that `else`. Empty statements are perfectly acceptable in C, and don't change the meaning of the code at all. `if (foo) {};` is no different from `if (foo) {}`. – Blacklight Shining Nov 01 '14 at 15:14
  • @BlacklightShining: Without `else`, the macro could gobble up a following `else`: `if (x) ASSERT(y) else z();` -- with the current version, this gives an error (good!) but if you omit the else in the macro definition, the `else z()` will attach inside the expansion of the `ASSERT` macro (bad!). Of course, this is why we use `do { ... } while(0)` in macros. – Dietrich Epp Jan 15 '15 at 19:24
  • Optimizing your ASSERT is a low hanging fruit. Makes sense to get it right and move on. – Sergei Dec 18 '19 at 09:29

3 Answers3

15

The performance gain is not likely to be significant, but this is how those linux kernel macros are defined:

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)

So, you could modify your condition like this (assuming that expr is expected to be true and therefore !(expr) is expected to be false):

if (__builtin_expect(!(expr), 0)) {

Or you could define the same macros as the kernel and use them for better readability.

This is gcc builtin, so not portable of course.

This suggests that clang also supports the builtin. Othrwise, you can use the above macros and conditionally define them like #define likely(x) (x) on compilers that don't support the builtin.

In your case, the prediction is going to be good (either that or you're aborting), so there shouldn't be a risk of pessimisation, but if you do consider using the builtin more widely, here's a word of advice from gcc documentation:

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.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 2
    gcc also have `-fprofile-generate` flag to instrument program with branch profiling, then run program to generate profiling data, and re-compile with `-fprofile-use`. It will automatically set expect-like predictions. – keltar May 28 '14 at 11:58
  • @keltar, good point. I added a quote from the documentation which also suggests profile feedback. – eerorika May 28 '14 at 12:07
  • likely is rather pointless, because when the brain/assert hits it looses many many many cycles – Quonux May 28 '14 at 12:09
11

For many CPUs, likely and unlikely (or anything else for that matter) don't provide a branch hint to the CPU (only to the compiler, which may use it to optimize differently, similar to profile guided optimization) for the simple reason that there is no way to do it.

For example, branch hints are defined for x86 since P4. Before that they had no effect, but it's even worse, they have no effect on anything except P4. So they're useless (but waste space and bandwidth), and as far as I know GCC does not emit them.

ARM doesn't (yet?) have branch hints either. PPC, IA64 and SPARC do have hinted branches, I don't know whether GCC uses likely and unlikely for them though, but at least it could.

harold
  • 61,398
  • 6
  • 86
  • 164
  • +1 for pointing out that instruction bandwidth is important, even when we live in a world full of cpu's with fancy multiple dispatching stuff from 16 bytes lines...i guess few know that :) – Quonux May 28 '14 at 12:22
  • 3
    I'm almost sure expect is more like hint to compiler to rearrange instructions to [possibly] reduce conditional jumps; it could have performance gains even without actual designated hinting instructions. But effect may be reduced by CPU's own hardware branch prediction. – keltar May 28 '14 at 14:14
  • This isn't entirely correct. When the branch predictor has no collected statistics, it still must do some prediction. And the default prediction made (at least on Intels) is that backward jumps are taken (loops) and forward jumps aren't taken (if's). By reordering the code compilers implicitly provide those hints. – Yakov Galka Apr 17 '15 at 22:31
  • @ybungalobill not necessarily, Core2 just uses whatever junk happens to be in the predictor whether it actually relates to the branch or not. So the hint doesn't work then. But it doesn't even count anyway. Relying on a default is specifically the absence of a hint. – harold Apr 17 '15 at 23:08
  • @harold: Don't know about Core2. But I cannot make sense of your last sentence. Nobody defined a 'hint' to necessarily be an opcode prefix. Nobody said that likely/unlikely must emit such prefixes. Code layout that takes advantage of the documented assumptions made by the processor is just as a valid hint as anything else. The likely/unlikely as well as PGO cause the compiler to reorder code to match those assumptions. In the end your likely/unlikely end up being implicit hints to the processors supporting this protocol. – Yakov Galka Apr 18 '15 at 12:17
  • @ybungalobill not necessarily a prefix, obviously. An implicit hint is an oxymoron. Not telling the CPU anything is not a hint, it's nothing. Literally nothing. It's as much of a hint as stacking small things atop big things is a hint to physics to not make the pile fall over. Not a hint - just using existing behaviour. – harold Apr 18 '15 at 12:30
2

There is no need for any additional annotation. The compiler already knows about abort being called very rarely (at most once per program execution), and so the compiler will consider the branch containing abort as the unlikely branch anyway. You can verify this by looking at the declaration of abort. In glibc it is declared as

extern void abort (void) __THROW __attribute__ ((__noreturn__));

and in Visual Studio 2013:

_CRTIMP __declspec(noreturn) void __cdecl abort(void);
pentadecagon
  • 4,717
  • 2
  • 18
  • 26