Compiler optimizations

Can you count on compilers to optimize your Java code?

Developers are accustomed to compilers from languages other than Java that support various optimizations. In development, code is typically compiled with optimizations turned off so that source-level debuggers can operate on nonoptimized code. Once code has been thoroughly debugged, compiler options are enabled to produce code that executes as fast and efficient as possible. This model is used because most 3GLs (third-generation languages) are advanced enough to provide stable and predictable optimizations that exploit existing software and hardware platforms.

Because current compiler technology is so reliable, some developers have depended on the compilers' optimization features to clean up sloppily developed code. Some compilers can hide coding inefficiencies, but none can hide poorly designed code. For example, the following code sample shows an array being initialized:

int a = 5; int b = 7; int *acc[10];

for (i=0; I<10;i++) *acc[i] = a + b;

Because a and b are invariant and do not change inside of the loop, their addition doesn't need to be performed for each loop iteration. Almost any good compiler optimizes the code. An optimizer moves the addition of a and b outside the loop, thus creating a more efficient loop. For example, the optimized code could look like the following:

int a = 5; int b = 7; int c = a + b;

int *acc[10];

for (i=0; I<10;i++) *acc[i] = c;

This is a common and simple example of invariant code optimization. Obviously, optimizations can be a lot more complex. That most Java compilers do little when it comes to optimizing code may be surprising. Some of the more common optimizations supported across Java compilers are constant folding and dead code elimination.

Constant folding

Constant folding refers to the compiler precalculating constant expressions. For example, examine the following code:

     static final int length = 25;
        static final int width = 10;
        int res = length * width;

Execution time is not used to multiply those values; instead, multiplication is done at compile time. The code for the following variable assignment is modified to produce bytecode that represents the product of width and length:

int res = 250;

Dead code elimination

Simple dead code elimination prevents the compiler from generating bytecode for blocks that don't get executed. Dead code elimination does not affect the code's runtime execution. It does, however, reduce the size of the generated classfile. For example, the two expressions in method nocode() are not converted to bytecode:

class Text { public static final boolean trace = false; public void nocode() { if (Test.trace) { <....> }

if (false) { <....> } } }

Code is still generated if an expression evaluates to false at runtime. The only time bytecode is not produced is when the expression evaluates to false at compile time.

Summary

Developers should realize that only a few optimizations are supported by most Java compilers. For those who use compilers that do not perform a lot of optimizations, three options may be considered:

  1. Create hand-optimized Java source code in an attempt to achieve better performance
  2. Rely on runtime optimizations such as JIT compilers or adaptive compiler technology (i.e., HotSpot)
  3. Use third-party optimizing compilers that compile Java source to optimized bytecode

As third-party compilers improve and gain acceptance, and competition between major compiler vendors heats up, Java compiler technology should improve.

Reggie Hutcherson evangelizes Sun's Java 2 platform technologies around the world, concentrating on J2SE and the HotSpot performance engine.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies