Remove unused methods and classes Increase statically bound calls Cut dead code and unnecessary instructions, including checks for null

- 62 -

3.4.1 What Optimizing Compilers Cannot Do

Optimizing compilers cannot change your code to use a better algorithm . If you are using an inefficient search routine, there may be hugely better search algorithms giving orders of magnitude speedups. But the optimizing compiler only tries to speed up the algorithm you are using with a probable small incremental speedup. It is still important to profile applications to identify bottlenecks even if you intend to use an optimizing compiler. It is important to start using an optimizing compiler from the early stages of development in order to tailor your code to its restrictions. More than one project I know of has found the cost of trying to integrate an optimizing compiler at a late stage of development too expensive. In these cases, it means restructuring core routines and many disparate method calls, and can even require some redesign to work around limitations imposed by being unable to correctly handle reflection and runtime class resolution. Optimizing compilers have difficulty dealing with classes that cannot be identified at compile time e.g., building a string at runtime and loading a class of that name. Basically, using Class.forName is not and cannot be handled in any complete way, though several compilers try to manage as best they can. In short, managers with projects at a late stage of development are often reluctant to make extensive changes to either the development environment or the code. While code tuning can be targeted at bottlenecks and so normally affects only small sections of code, integrating an optimizing compiler can affect the entire project. If there are too many problems in this integration, most project managers decide that the potential risks outweigh the possible benefits and prefer to take the safe route of carrying on without the optimizing compiler.

3.4.2 What Optimizing Compilers Can Do

Compilers can apply many classic optimizations and a host of newer optimizations that apply specifically to object-oriented programs and languages with virtual machines. I list many optimizations in the following sections. You can apply most classic compiler-optimization techniques by hand directly to the source. But usually you should not, as it makes the code more complicated to read and maintain. Individually, each of these optimizations improves performance only by small amounts. Collectively as applied by a compiler across all the code, they can make a significant contribution to improving performance. This is important to remember: as you look at each individual optimization, in many cases the thought, Well, that isnt going to make much difference, may cross your mind. This is correct. The power of optimizing compilers comes in applying many small optimizations automatically that would be annoying or confusing to apply by hand. The combination of all those small optimizations can add up to a big speedup. Optimizing-compiler vendors claim to see significant speedups: up to 50 for many applications. Most applications in serious need of optimization are looking for speedups even greater than this, but dont ignore the optimizing compiler for that reason: it may be doubling the speed of your application for a relatively cheap investment. As long as you do not need to restructure much code to take advantage of them, optimizing compilers can give you the biggest bang for your buck after JIT VMs in terms of performance improvements. The next sections list many of the well-known optimizations these compilers can apply. This list can help you when selecting optimizing compilers, and also can help if you decide you need to apply some of these optimizations by hand.

3.4.2.1 Remove unused methods and classes

- 63 - When all application classes are known at compile time, an optimizing compiler can analyze the full runtime code-path tree, identifying all classes that can be used and all methods that can be called. Most method calls in Java necessarily invoke one of a limited set of methods, and by analyzing the runtime path, you can eliminate all but one of the possibilities. The compiler can then remove unused methods and classes. This can include removing superclass methods that have been overridden in a subclass and are never called in the superclass. The optimization makes for smaller download requirements for programs sent across a network and, more usefully, reduces the impact of method lookup at runtime by eliminating unused alternative methods.

3.4.2.2 Increase statically bound calls

An optimizing compiler can determine at compile time whether particular method invocations are necessarily polymorphic and so must have the actual method target determined at runtime, or whether the target for a particular method call can be identified at compile time. Many method calls that apparently need to have the target decided at runtime can, in fact, be uniquely identified see the previous section. Once identified, the method invocation can be compiled as a static invocation, which is faster than a dynamic lookup. Static methods are statically bound in Java. The following example produces in superclass if method1 and method2 are static, but in subclass if method1 and method2 are not static: public class Superclass { public static void mainString[] args {new Subclass .method1 ;} public static void method1 {method2 ;} public static void method2 {System.out.printlnin superclass ;} } class Subclass extends Superclass { public static void method2 {System.out.printlnin subclass ;} }

3.4.2.3 Cut dead code and unnecessary instructions, including checks for null

Section 14.9 of the Java specification requires compilers to carry out flow analysis on the code to determine the reachability of any section of code. The only valid unreachable code is the consequence of an if statement see Section 3.5.1.4 . Invalid unreachable code must be flagged as a compile error, but the valid code from an if statement is not a compile error and can be eliminated. The if statement test can also be eliminated if the boolean result is conclusively identified at compile time. In fact, this is a standard capability of almost all current compilers. This flow analysis can be extended to determine if other sections and code branches that are syntactically valid are actually semantically unreachable. A typical example is testing for null . Some null tests can be eliminated by establishing that the variable has either definitely been assigned to or definitely never been assigned to before the test is reached. Similarly, some bytecode instructions that can be generated may be unnecessary after establishing the flow of control, and these can also be eliminated.

3.4.2.4 Use computationally cheaper alternatives strength reduction