Last active
March 1, 2019 07:24
-
-
Save kkm000/2cbc3f07ea19e36f1190ba80c88d0d2a to your computer and use it in GitHub Desktop.
How compilers decide which branch is conditionally jumped to
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TL;DR
-O1
/-O2
combination. It does not matter which way you write the condition in an assertion check.[[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.
-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.-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.