Skip to content

Instantly share code, notes, and snippets.

@kkm000
Last active March 1, 2019 07:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kkm000/2cbc3f07ea19e36f1190ba80c88d0d2a to your computer and use it in GitHub Desktop.
Save kkm000/2cbc3f07ea19e36f1190ba80c88d0d2a to your computer and use it in GitHub Desktop.
How compilers decide which branch is conditionally jumped to
// Try this code on godbolt.com: https://godbolt.org/z/oXGnoY
// Command line: -std=c++11 -O1 (or -O2).
#ifdef _MSC_VER
#define __func__ __FUNCTION__
#endif
#define KALDI_ASSERT(cond) do { if (cond) (void)0; else \
KaldiAssertFailure_(__func__, __FILE__, __LINE__, #cond); } while(0)
#define KALDI_ASSERT_INVERTED1(cond) do { if (!(cond)) \
KaldiAssertFailure_(__func__, __FILE__, __LINE__, #cond); } while(0)
#define KALDI_ASSERT_INVERTED2(cond) (void)((cond) || \
(KaldiAssertFailure_(__func__, __FILE__, __LINE__, #cond), 0))
#define KALDI_ASSERT_INVERTED3(cond) (void)(!(cond) && \
(KaldiAssertFailure_(__func__, __FILE__, __LINE__, #cond), 0))
// Try commenting out the attribute!
[[ noreturn ]]
void KaldiAssertFailure_(const char *func, const char *file,
int line, const char *cond_str);
int some_fun(int); // Let's have some fun!
int AsInKaldi(int num) {
KALDI_ASSERT(num > 42);
return some_fun(num) + 1;
}
int Inverted1(int num) {
KALDI_ASSERT_INVERTED1(num > 42);
return some_fun(num) + 1;
}
int Inverted2(int num) {
KALDI_ASSERT_INVERTED2(num > 42);
return some_fun(num) + 1;
}
int Inverted3(int num) {
KALDI_ASSERT_INVERTED3(num > 42);
return some_fun(num) + 1;
}
@kkm000
Copy link
Author

kkm000 commented Mar 1, 2019

TL;DR

  • In one compilation, all 4 conditions are always decided the same way by the same compiler and the -O1/-O2 combination. It does not matter which way you write the condition in an assertion check.
  • With [[noreturn]] on the assertion failure handler, compiler tend to predict assertion success. With -O2, they always use the hint.

Longer exposure

I compared the assembly code generated from this sample by gcc, clang, icc and cl (aka msvc) on Godbolt's Compiler Explorer site, for the highest minor version offered of every major version (for clang, I also added 3.0.0, 3.6 and 3.8). None at all produced different code for the condition in assert in any of 4 cases.

  • With -O1, the only compilers that make a prediction that the assert condition would be false are gcc 5.x and earlier, and, amazingly, Intel's icc 13.x, which was supposed to know the best.
  • With -O2, all compilers without exception generate a conditional jump instruction for the condition being false.

It seems that [[noreturn]] is a strong hint that the function is less likely to be called.

Commenting out the [[noreturn]] did not change the consistency; like before, all compilers I tried decide the same way for all 4 functions.
Which way they decide has changed, however. With -O1, clang, msvc, gcc 5.x and earlier, and icc 13.x think the condition will be false, while gcc and icc 14.x+ bet on true. With -O2, gcc 6.x and 7.x (but not 8.x) changed their mind (to true), as did icc 13.x (to false). But even so, the way the condition is written made no difference, with either -O1 or -O2.

Is this relevant?

Probably not much. For hand-optimization, there is also __builtin_expect in gcc and some other compilers. Intel's icc produces nice reports and suggestions. But for code that is hit often, the branch predictor of a modern CPU will probably make a decent job. And PGO is unquestionably the ultimate way to go for optimizing production code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment