Book Excerpt: Understanding Compiler Optimizations
In this excerpt from his book "Write Great
Code," Randall Hyde introduces the common
optimizations that compilers perform, and when a
programmer should consider using or disabling them.
By Randall Hyde, LinuxWorld.com, 08/24/06
Later chapters will provide complete definitions and examples of common compiler optimizations in programming contexts where
compilers typically use them. But for now, here's a quick preview of the basic types.
- Constant folding
- Constant folding computes the value of constant expressions or subexpressions at compile time rather than emitting code to
compute the result at runtime.
- Constant propagation
- Constant propagation replaces a variable access by a constant value if the compiler determines that the program assigned that
constant to the variable earlier in the code.
- Dead code elimination
- Dead code elimination is the removal of the object code associated with a particular source code statement when the program
will never use the result of that statement, or when a conditional block will never be true.
- Common subexpression elimination
- Frequently, part of an expression will appear elsewhere in the current function. If the values of the variables appearing
in this subexpression haven't changed, the program does not need to recompute the value of the expression. The program can
save the value of the subexpression on the first evaluation and then use that value everywhere else that the subexpression
appears.
- Strength reduction
- Often, the CPU can directly compute a value using a different operator than the source code specifies. For example, a shift
operation can implement multiplication or division by a constant that is a power of 2, and certain modulo (remainder) operations
are possible using bitwise
and instructions (the shift and and instructions generally execute much faster than the multiply and divide instructions). Most compiler optimizers are good
at recognizing such operations and replacing the more expensive computation with a less expensive sequence of machine instructions.
- Induction
- In many expressions, particularly those appearing within a loop, the value of one variable in the expression is completely
dependent upon some other variable. Frequently, the compiler can eliminate the computation of the new value or merge the two
computations into one for the duration of that loop.
- Loop invariants
- The optimizations so far have all been techniques a compiler can use to improve code that is already well written. Handling
loop invariants, by contrast, is a compiler optimization for fixing bad code. A loop invariant is an expression that does
not change on each iteration of some loop. An optimizer can compute the result of such a calculation just once, outside the
loop, and then use the computed value within the loop's body. Many optimizers are smart enough to discover loop invariant
calculations and can use code motion to move the invariant calculation outside the loop.
-
Good compilers can perform many other optimizations. However, there are the standard optimizations that you should expect
any decent compiler to do.
Controlling Compiler Optimization
By default most compilers do very little optimization: You must explicitly tell the compiler to perform any optimization.
This might seem counterintuitive; after all, we generally want compilers to produce the best possible code for us. However
there are many definitions of "optimal," and no single compiler output is going to satisfy every possible definition for this
term. Therefore, most compilers enable optimization only when you explicitly tell them to.