We have basic code optimization techniques, JIT and adaptive compilers to optimize, then why we need Java Code Optimization?
After using general optimization also there are many small language dependent things that consumes resources and can have catastrophic effect on our code.
And to understand why we need optimization we must know about what cost is associated with each operation we perform.
Cost associated with operations (Approx, where 1 unit minimum cost associated with any expression)
- Variable Declaration - 1 Unit
- Assignment Operation - 1 Unit
- Method Calls - 3 to 30 Units
- Object Invocation - 3000 to 5000 Units
- Broadening of Object - 1000 to 3000 Units
- Overloaded/Overridden Methods Calls - 500 to 3000 Units
Above figures just gives an idea about how much resources (time/memory etc.) we consume in a simple code.
To better concur the optimization problem we will discuss each bottleneck one by one, to get rid of them and how to tradeoff to improve performance.
Lets start with our largest enemy.
Problems Associated with Objects is
- Excessive Memory Usage
- Time Consumed For Creation
- Memory Cycling by GC (Threshing)
- Whole Object Graph Creation (All super classes must also be initialized)
- Memory Leak - if not freed up.
Object Creation Guidelines
- Avoid creating objects in frequently used routines; possibly pass in reusable objects as parameters.
- Avoid creating too many object members, as they are instantiated at the time of creation of object.
- Pre-size collections - this could give significant performance boost, try to initialize a collection slightly larger than required on average basis.
- Reuse objects - thus avoiding new object creation and also garbage collection.
- Avoid intermediate object creation. Example -
- Expression int x = new Integer (str).intValue(), where str="123"
- Can be written as int x = Integer.parseInt(str).
Second big monster family to trouble us - String and char array associated with each String object
Problems with Strings
- String is immutable, thus no customizations can be done.
- Has underlying char array data store (a secondary object in Java), and usually Strings allocate more memory to them than required.
- Conversions to other Objects or types are costly.
But Sun did provided some great features to enhance performance
- Very efficient comparison methods, namely equals and equalsIgnoreCase, and they compare them by length and then from left to right.
- subString returns a new string object but they do share the same underlying array object just the start and offset location is changed.
- String has special relationship with StringBuffer class witch is more flexible and conversion from later to first does not cost much as they share same internal array with just a flag, shared, indicating that array is shared.
Okay we know it now, but how this could help us?
If we keep these in mind we can write code more efficient for example
from point 1: - While comparing two String tokens (of same length) the equals (or equalsIgnoreCase) will return faster as early as the difference is, so we can choose our literals in that way. That is choosing "APPLY_RUN_SCHEDULE" and "CANCEL_RUN_SCHEDULE" than using "RUN_SCHEDULE_APPLY" and "RUN_SCHEDULE_CANCEL" for greater performance.
From point 3: - While concatenation of strings it is more efficient to use append method of StringBuffer class and then converting the result to String. That is
return "abc" + "def" + "ghi"
can be written as
return (new StringBuffer()).append("abc").append("def").append("ghi")
return (new StringBuffer("abc")).append("def").append("ghi")
as the conversion from StringBuffer to String is not costly (remember special relation).
Try-Catch block itself does not add any cost to the overall performance, but if at all an exception occurs a significant change in performance can be observed, and mostly it is due to cost of tracing done by JVM for the stack trace. This cost can be in terms of hundreds or thousands units of normal code execution.
Question: How to optimize?
Answer: The best way to optimize is to reuse the exception caught to throw to next level of code so that stack trace is not populated again.
And also we should not catch a more general exception in catch clause if we already know the type of exception we could get, as this would add up object generalization cost to the exception handling.
Casting involves convertibility validation cost and conversion cost. Usually primitive casting does not involve any cost but casting any object to some class or interface is costly (while casting to interface is more costly).
Most of the time casting is required while using collections, but one can use type specified collection classes to avoid casting. Also to avoid Exceptions (witch are very costly) one can use instanceof operator before casting.
Some tips to use variables efficiently are
- Local and argument variable are cheaper as they consume only 4 bytes on the stack.
- Arrays are costly as additional overhead is required for checking boundary condition.
- Integer calculations are faster than char, long, byte or short, as they needed to rollup to the integer for calculations.
- Long and double are twice slower as they are platform dependent.
Optimizing Loops and Conditional flows
- General optimization techniques (code motion and loop unrolling)
- Termination point or condition should not be a method.
- Index or counter should be an int (not an char, short, byte or long)
- Counting down is faster that is i++ is faster than i--
- Use Short-Circuit (&&, ||) operators to combine conditions efficiently.
These are most common Java optimization guidelines, there are still many areas where Java can be optimized for greater performance, and I would like to leave them for you to search new possibilities and to come out with your own optimizing guidelines.