views:

388

answers:

11

Hello,

When can optimizations done by the compiler cause my C++ code to exhibit wrong behaviour which would not be present had those optimizations not been performed? For example, not using volatile in certain circumstances can cause the program to behave incorrectly (e.g. not re-reading the value of a variable from memory and instead only reads it once and stores it in register). But are there other pitfalls which one should know about before turning on the most aggressive optimization flag and afterwards wondering why the program doesn't work anymore?

+4  A: 

Besides the case you mentioned, timing can change in multi-threaded code such that what appears to be working no longer does. Placement of local variables can vary such that harmful behaviour like a memory buffer overrun occurs in debug but not release, optimized or non-optimized, or vice versa. But all of these are bugs that were there already, just exposed by compiler option changes.

This is assuming the compiler has no bugs in its optimizer.

Steve Townsend
+3  A: 

I've only run into it with floating point math. Sometimes the optimizations for speed can change the answer a little. Of course with floating point math, the definition of "right" is not always easy to come up with so you have to run some tests and see if the optimizations are doing what you're expecting. The optimizations don't necessarily make the result wrong, just different.

Other than that, I've never seen any optimizations break correct code. Compiler writers are pretty smart and know what they're doing.

miked
+10  A: 

Compiler optimizations should not affect the observable behavior of your program, so in theory, you don't need to worry. In practice, if your program strays in to undefined behavior, anything could already happen, so if your program breaks when you enable optimizations, you've merely exposed existing bugs - it wasn't optimization that broke it.

One common optimization point is the return value optimisation (RVO) and named return value optimization (NRVO) which basically means objects returned by value from functions get constructed directly in the object which is receiving them, rather than making a copy. This adjusts the order and number of constructor, copy constructor and destructor calls - but usually with those functions correctly written, there's still no observable difference in the behavior.

AshleysBrain
Does this apply to multi-threading as well?
gablin
@gablin: Yes. Although C++0x will specify this in the standard more clearly, I believe. C++ lacks good threading standards. But optimizing compilers are designed to not do anything observable except make things faster/use less memory/be more efficient.
Scott Stafford
I try not to should on anybody or anything. If my compiler can offer an optimization that is unsafe when certain conditions are met yet dramatically improve performance when those conditions are not met, I want my compiler to offer those optimizations.I expect the unsafe optimizations will be turned off by default, and that the compiler will adequately document the "certain conditions" so that I can make an informed decision about turning the optimizations on.
bbadour
I reckon this subject has been covered enough by now. All the answers have been of much use, thanks everyone! I'll set this as accepted since it has gotten the most votes and pretty much covers the core of it all.
gablin
+1  A: 

I just recently saw that (in C++0x) the compiler is allowed to assume that certain classes of loops will always terminate (to allow optimizations). I can't find the reference right now but I'll try to link it if I can track it down. This can cause observable program changes.

Mark B
I would love to see if someone can provide a practical example where this would affect behavior in a real system.
tenfour
...allowed to assume that **a certain, limited class of** loops will always terminate...
dmckee
http://stackoverflow.com/questions/3592557/optimizing-away-a-while1-in-c0x @tenfour: There are a few examples in the linked articles involving embedded systems where infinite loops are legitimately useful, but they seem pretty limited in scope.
Dennis Zickefoose
@tenfour: example here, http://blog.regehr.org/archives/161. Basically a program which searches for a counter-example for Fermat's Last Theorem, it's not especially contorted code. Most people reading it would think it never terminates: the Theorem is true. In fact, it has undefined behaviour, because it contains a loop which the compiler is permitted to assume terminates, but which, if not for that obscure clause in the standard, certainly would not terminate. With Intel's compiler, on Windows, it terminates stating that Fermat's Last Theorem is false.
Steve Jessop
Basically, compilers are allowed to move code that wouldn't execute until after a loop terminates, so that the latter code will execute before or during execution of the loop, provided that--in the event the loop does terminate--the observable sequence of events would not be affected by the rewrite (even though their timing might be).
supercat
A: 

I do not have the exact details (maybe someone else can chime in), but I have heard tell of a bug caused by loop unrolling/optimization if the loop counter variable is of char/uint8_t type(in a gcc context i.e.).

decimus phostle
A: 

Strict aliasing is an issue you might run into with gcc. From what I understand, with certain versions of gcc (gcc 4.4) it gets automatically enabled with optimizations. This site http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html does a very good job at explaining strict aliasing rules.

5ound
+1  A: 

Just don't work from the assumption that the optimizer ever destroys your code. It's just not what it was made to do. If you do observe problems then automatically consider unintentional UB.

Yes, threading can play havoc with the kind of assumptions you are used to. You get no help from either the language or the compiler, although that's changing. What you do about that is not piss around with volatile, you use a good threading library. And you use one of its synchronization primitives wherever two or more threads can both touch variables. Trying to take short-cuts or optimizing this yourself is a one-way ticket into threading hell.

Hans Passant
Don't start by assuming the compiler or optimizer is broken, but don't always leave out that possibility. Often times, I find examining the compiler output for some code that's behaving strangely is useful. On rare occasions, it's exposed compiler bugs; on many more occasions, it's revealed how the compiler was interpreting what I wrote (things like interactions between signed and unsigned types can be tricky).
supercat
I'm having trouble understanding your first line: do you mean I should not assume that the compiler _always_ destroys my code, or that it _never_ destroys my code? And what does "UB" stand for? On a completely different note, I think `volatile` will haunt me forever after having asked those previous questions of mine. ^^ A better name for that keyword would have been `violated`, because that's how I feel after having used it.
gablin
@gablin: He's saying start by looking at your code for bugs; always assume the compiler is correct until you're fairly certain your own code is not the cause of the problems you witness.
Dennis Zickefoose
@gablin: agreed with Dennis. UB = Undefined Behavior. Yes, it probably wasn't a good idea to bring up *volatile*, just mention threading next time, I guess. You are chasing a volatile ghost, it is rarely seen.
Hans Passant
A joke comment, but also serious: When I started programming on Unix the compiler destroyed my code. No, really it did! When typing cc -o program program.c on the command line it is awfully easy for shell tab-completion to make that cc -o program.c program.c
Zan Lynx
@Zan: ah, Unix hater's handbook. Great read :)
Hans Passant
@Hans Passant: Yeah, but at least I've gotten to know more about it. So one way or another, it was worth it. ^^ Thanks.
gablin
+1  A: 

At a meta level, if your code uses relies on behavior that is based on undefined aspects of the C++ standard, a standards conforming compiler is free to destroy your C++ code (as you put it). If you don't have a standards conforming compiler, then it can also do non-standard things, like destroy your code anyway.

Most compilers publish what subset of the C++ standard they conform to, so you can always write your code to that particular standard and mostly assume you are safe. However, you can't really guard against bugs in the compiler without having encountered them in the first place, so you still aren't really guaranteed anything.

MSN
+2  A: 

Failing to include the volatile keyword when declaring access to a volatile memory location or IO device is a bug in your code; even if the bug is only evident when your code gets optimized.

Your compiler will document any "unsafe" optimizations where it documents the command-line switches and pragmas that turn them on and off. Unsafe optimizations usually related to assumptions about floating point math (rounding, edge cases like NAN) or aliasing as others have already mentioned.

Constant folding can create aliasing making bugs in your code appear. So, for example, if you have code like:

static char *caBuffer = "                                         ";

...

strcpy(caBuffer,...)

Your code is basically an error where you scribble over a constant (literal). Without constant folding, the error won't really effect anything. But much like the volatile bug you mentioned, when your compiler folds constants to save space, you might scribble over another literal like the spaces in:

printf("%s%s%s",cpName,"   ",cpDescription);

because the compiler might point the literal argument to the printf call at the last 4 characters of the literal used to initialize caBuffer.

bbadour
+2  A: 

Bugs caused by compiler optimizations that are not rooted in bugs in your code are not predictable and hard to determine (I managed to find one once when examining the assembly code a compiler had created when optimizing a certain area in my code once). The common case is that if an optimization makes your program unstable, it just reveals a flaw in your program.

karx11erx
+1  A: 

As long as your code does not rely on specific manifestations of undefined/unspecified behavior, and as long as the functionality of your code is defined in terms of observable behavior of C++ program, a C++ compiler optimizations cannot possibly destroy the functionality of your code with only one exception:

  • When a temporary object is created with the only purpose of being immediately copied and destroyed, the compiler is allowed to eliminate the creation of such temporary object even if the constructor/destructor of the object has side-effects affecting the observable behavior of the program.

In the newer versions of C++ standard that permission is extended to cover named object in so called Named Return Value Optimization (NRVO).

That's the only way the optimizations can destroy the functionality of conforming C++ code. If your code suffers from optimizations in any other way, it is either a bug in your code or a bug in the compiler.

One can argue though, that relying on this behavior is actually nothing else than relying on a specific manifestation of unspecified behavior. This is a valid argument, which can be used to support the assertion that under the above conditions optimizations can never break the functionality of the program.

Your original example with volatile is not a valid example. You are basically accusing the compiler of breaking guarantees that never existed in the first place. If your question should be interpreted in that specific way (i.e. what random fake non-existent imaginary guarantees can optimizer possibly break), then the number of possible answers is virtually infinite. The question simply wouldn't make much sense.

AndreyT