- Preincrement Is More Efficient!
- Goto Considered Faster?
- String Theory
- Cargo Cult Languages
- Don't Try This at Home
Goto Considered Faster?
A few times recently, I've seen code that looked a bit like this:
if (someCall()) goto cleanup1; if (anotherCall()) goto cleanup2; return something; cleanup2: cleanupStuff(); cleanup1: cleanupMoreStuff(); return NULL;
This antipattern is used when writing code that initializes something complex and needs to clean up if some of the intervening steps fail. Those people who learned to program after the invention of structured programming, sometime in the late '60s, will recognize that this is an incredibly ugly way of writing:
if (!someCall()) { if (!anotherCall()) { return something; } cleanupStuff(); } cleanupMoreStuff(); return NULL;
This version is clearer: The scope indicates clearly which cleanup corresponds to which part of the initialization. It's also shorter. I finally tracked down one of the people who was responsible for some code in the first form, and asked him what he was thinking.
Apparently, the first form is faster because the compiler will only insert one branch. Okay, I thought, that's easy to test. It seemed unlikely, because they're semantically equivalent, but don't ever take the compiler doing the right thing on faith. Put both of these in a simple file and compile it with gcc -S. Both were the same. Maybe it's something to do with the optimizations? Compile with -O0, and they're still the same.
Perhaps, I thought, GCC is especially good at this case, so I tried with clang. Finally, a difference! With optimizations, they produce the same code. Without optimizations… the first one is slower. So, code that was intentionally obfuscated to make it faster is never faster than the clearer version, and is slower in some cases.
After a little bit of thinking, I realized that I was doing something wrong even thinking about the speed here. Each of the initialization calls is complex function. Often it's a single system call, but sometimes it's several. The cost of a single system call dwarfs the cost of a single one of the branches to such an extent that even if the second one made twice as many branches and they were all mispredicted, it would still make a tiny difference to the overall speed.