bloodline wrote:Ok, so I haven’t been using any optimisation options, for two reasons;
Oops. A big one.
bloodline wrote:Firstly I’m quite old school, and was taught to only apply compiler optimisation after you were happy with the performance of the code as written by hand, and secondly my motivation for this project comes from wanting to learn more about the modern PC architecture.
I’m going to experiment with some compiler options now!
-edit-
-O2 seems to produced the fastest code, -O3 just crashes
Being "old school" may explain the -O3 crashes.
Today's computers enable today's compilers to analyze and optimize code in ways impractical some 30 years ago. What old compilers simply couldn't do today's do easily.
This has two important effects.
The most obvious one is that you get fast code without having to resort to assembly or lots of trivial manual optimizations.
The least obvious effect is that today's compilers aren't looking at source code (or its internal representation) through a keyhole, they are seeing lots of it at once and remembering a lot of what they have seen for much longer. This means that they can (and do) make and apply decisions across large chunks of code. Specifically, if your code has what's known as "undefined behavior" (which has existed since the first C language standard of 1989, btw), this undefined behavior may drive your compiler into generating "broken" code that doesn't work how you want or expect it to. Because of undefined behavior, compiled code can be "broken" in some very strange ways, such that the effects of the undefined behavior aren't localized to the line or statement where it occurs at the source code level, but are far removed from there. Old compilers couldn't do such analysis across large chunks of code and therefore undefined behavior was usually limited to where it occurred at the source level and so the effects of UB were immediate and easy to deal with. Often times there were no perceived ill effects of UB and things "just worked". This is not so anymore. And it's not because today's compilers are somehow mean and evil. The C language standard allowed old compilers to be mean and evil just as well. It's just that it took some technological progress for that meanness to manifest.
Allow me to show you an example of completely unexpected undefined behavior that I've been bitten by years ago:
Code: Select all
void f(int a, int position) {
int bit = (a & (1 << position)) != 0;
if (bit)
puts("bit=1");
else
puts("bit=0");
}
If position is too large, f() may end up crashing instead of printing a bogus value. The reason is that the compiler can generate the 80386 bit instruction instead of a sequence of shl & and. If I remember correctly, the bit instruction accesses memory at address (&a + position/32). So, when position is in the valid range, everything's OK. When it's not, the benign-looking shift unexpectedly crashes your program.
There are many more examples of undefined behavior, it's just one that resulted a totally unexpected crash.
My understanding is that Linus Torvalds writes the Linux kernel as if there was no technological progress or undefined behavior and it still was 1988. He does so by disallowing specific optimizations in the compiler. And he probably keeps adding such disallowing options in the makefile. I think it's a rather unfortunate situation with deliberate misuse of the language and compiler. Perhaps, using a different language, with which he wouldn't need to fight, would be better.
I think you should learn about undefined behavior and eradicate it and enjoy compiling your code with high levels of optimization.