Let's say you know C and are thinking about learning Golang. Let's also say that you prefer not to use a debugger [1]. This probably means you need to create a debug version of your Golang code. A way to do that is with constant folding [2], for example:
const constDebug = 0
if (1 == constDebug) { debugCode() } // if() compiled away due to constant folding
var localDebug uint64 = 0
if (1 == localDebug) { debugCode() } // if() NOT compiled away due to no constant
Using constant folding you can add an arbitrary amount of permanent debug instrumentation to your code without bloating or slowing down the release version of the code.
Some languages have constant folding and some don't. For example C has a kind of constant folding via its preprocessor. Perl has constant folding, but PHP has no constant folding. Golang does have constant folding but how to set a constant at compile time in order to create a release or debug version of the code? This is tricky with Golang but it can be done via command line tags [3].
Okay, so we have a way to build a release and debug version of code in both C and Golang. Let's create some tests to compare performance. Let's compare the performance of release vs debug versions of the code in each language. Let's compare the different compilers for C, e.g. gcc vs clang. Let's compare the affect of using local vs global variables, which might be treated differently by the optimizer? Let's compare the effect of constant folding even in the release version, e.g. similar to switching on and off custom functionality at compile time. Let's compare the cost of inline vs function debug code in Golang, for example:
if (1 == constDebug) { debugFunc() } // uglier debug inline
debugFunc() // nicer debug function
In the Golang release version then how will an empty function affect performance, for example:
func debugFunc() {
// nothing here in Golang release version
}
The code in this gist contains such tests implemented in both C and Golang. Each variation consists of a loop with small variations of if()s and business logic. The loop iteration count is larger because it's easier and more meaningful to time the entire loop than an individual iteration inside the loop. Results on a Dell laptop with Xeon CPU are also included, togther with the results graphed.
Observations in general:
- Using global variables appears to be generally slower than using local variables, regardless of the language or compiler.
Observations on C with gcc:
- Naively, we would expect the local variable release versions to be ranked from fastest to slowest as CONST_FALSE, CONST_TRUE, local_var_false, local_var_true due to increasing amount of code executed.
- However, the local variable release versions actual results ranked from fastest to slowest were local_var_true, CONST_TRUE, local_var_false, CONST_FALSE.
- Interestingly, local_var_true is predicted as being the slowest but ends up as being by far the fastest, even though the code is exectuing at extra if() and an extra increment.
- A couple of the compiled debug variants are mysteriously twice as slow as other variants, suggesting gcc optimization bugs?
- These results make clear that even only considering the gcc, it's difficult to make assumptions that the compiler optimizer is doing a good job.
Observations on C with gcc vs C with clang:
- None of the clang compiled variations approached the speed of the fastest gcc compiled variation.
- The global variable clang compiled variations are significantly slower than the same gcc compiled variations.
- Except for the release global varible variations, the obviously buggy gcc debug variations, and the extremely fast local_var_true gcc release variation, clang offers a competitive alternative to gcc.
Observations on Golang:
- Using global variables in Golang can slow down the code by 2 to 3 times.
- In the release version, half the time inline debug is faster, and half the time function debug is faster.
- In the debug version with local variables, function debug is around twice as slow as inline debug.
Observations on Golang vs C:
- Golang compile times were typically at least three times longer than using either C compiler.
- For release variations with debug inline compiled away then Golang can be anywhere between 40% and 388% slower than C, and 248% slower on average based upon the 8 release code variations.
- For release variations with debug functions compiled away then Golang can be anywhere between 5% and 397% slower than C, and 212% slower on average based upon the 8 release code variations.
- Because both C and Golang compilers optimize unpredictably then its very difficult to determine whether tiny code tweaks in either language could result in performance gold.
[1] https://lemire.me/blog/2016/06/21/i-do-not-use-a-debugger/ [2] https://en.wikipedia.org/wiki/Constant_folding [3] https://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool