Trying to outsmart a compiler defeats much of the purpose of using one.
Kernighan and Plauger The Elements of Programming Style.
I have seen many people try to outsmart compilers (sometimes that person was me) and with rare exceptions, the compiler has won every time.
I have seen a programmer who had just read Mike Abrash’s “Zen of Assembly Language” code up their own memcpy routine using all the the clever optimizations in the book, only to discover that the compiler writers know all of these optimizations too. I have seen people try and get clever with switch statements and implement them as jump tables. Steve Johnson’s portable C compiler optimized switch statements automatically in the 80s. I have seen a programmer who was an expert on the 68000 instruction set (and he genuinely was an expert) come unstuck because we were now compiling for the 68040 and the instruction timings had changed.
In the late 90s there was a discussion on one of the games programming newsgroups about using left shift instead of multiply. Rather than this:
j = i * 2;
you’d write:
j = i << 1;
and get a wonderful speed increase. I decided to run some tests on this. I coded up a little test program with two functions:
int multiplyByTwo( int i ) { return i * 2; }
int shiftLeftByOne( int i ) { return i << 1; }
I ran the program through Watcom with our normal release build settings, then looked at the assembler output. Watcom had replaced i * 2
with i << 1
, it had realized that the two functions were the same so it had merged them, then it inlined the function. Furthermore, since I was using constants to keep the program simple it calculated the results at compile time. I think that the only thing left by the end was a call to printf with a couple of constants. Compilers are good at this stuff.
There's another problem - if we are going to try doing these replacements ourselves we have to make sure that we get them right. Replacing multiply with a left shift is easy and safe to do, however, dividing by two is not necessarily the same as shifting right by one - under some circumstances you can safely use the shift, but under others you can't. I have always found that a compiler is far more likely to get these things right than I am.
The moral is for us to write code that expresses exactly what we want (if we want a multiplication then we should write a multiplication) and let the compiler do whatever it feels is appropriate. Compilers are really good at peephole optimizations. As programmers we are much better off using our time to replace that O(n^2) algorithm with an O(n log n) algorithm.