Performance Guideline¶
Intent¶
The intent of this document is to provide guidelines for the development of Java code from a performance perspective. The statements range in importance from imperative (no system print commands) to guidance (algorithm order). However, remember not to optimize now but just be aware of performance. These should be taken as guidelines and applied where appropriate. Look at the context of the software to see if each guideline applies. Optimizing in one area with a specific use case in mind my adversely affect other areas and other use cases. Also, we operate in a multi-core CPU environment. Tradeoffs that made sense 10 years ago don't necessarily hold true when you have multiple compute cores to process events in parallel.
General¶
- What is the order of the algorithm? Is there a better one?
- Does it follow the exception and error reporting guidelines?
- Is the code minimal for what it is required to do? Does that adversely affect maintenance?
- Consider broader use cases.
- Are transcendentals (trig, exponents, powers, logs, roots) used only when necessary? Some of these may be hidden. For example, the distance() method on coordinates is calling square-root, but distanceSquared is not. In almost every case, distanceSquared() should be used instead of distance().
- Does it use modern algorithmic approaches (no easy bubble sorts, good choices of hash keys)?
- Uses numeric to string conversions only when necessary. If they are necessary, are they guarded?
- Are there any System.out.println() calls? These should be used only in very few, specialized places in the system.
- Does it use sound geometric reasoning approaches – (i.e., use distance squared thresholds)?
- It should eliminate repeated casts by casting once and holding the cast item in a correctly typed variable.
- Does the code perform any unnecessary coordinate conversions? Is it using the suggested coordinate representation consistently?
- Be careful of calling toString() within often-executed code, especially when the toString() ends of performing expensive operations.
- Understand services that you are calling, and what effects those services have.
Collections¶
- Minimize use of collections with volatile sizes, trimming can be detrimental to performance.
- Use of collection.size() should only be done when necessary. Most collections provide methods to determine if they are empty (isEmpty()), and those typically outperform collection.size() == 0 tests.
- Use iterator() or the new Java 1.5 'Enhanced for Loop' feature to iterate through collections rather than trying to mimic that functionality using a counter to increment up to the collection.size(). This is because the call to iterator() is 1) not expensive and 2) reduces errors later if the collection changes while you are traversing it. Using the 'Enhanced for Loop' approach is even better, as it reduces the amount of code written which reduces opportunities for errors. See http://java.sun.com/developer/technicalArticles/releases/j2se15/ for more information. That is, Remove the temptation to prematurely optimize iterative loops on collections by following the
int size = Collection.size(); for (int i = 0; i < size; i++) { do something; }
Instead, use iterator()
Iterator I = Collection.iterator(); While (I.hasNext()) { Class foo = (Class)i.next(); }
or better yet -
ArrayList<Integer> list = new ArrayList<Integer>(); for (Integer i : list) { ... }
Why? The lower case reduces the risk of ConcurrentModificationExceptions and IndexOutOfBoundExceptions, and often the original expense of calling size() on the collection far outweighs the cost of invoking the iterator.
- Set the "growth" factor of the collection and pre-size collections in-line with the data’s characteristics in situations where you are creating creating long-lived collections and have a notion what the size would be. Without this, the JVM will allocate a default number of elements in a collection for you.
- Consider using IdentityHashSets.contains() in places where you are linearly searching through a list for a specific element, and for whatever reason, you can't use a map.
- Use type safe collections (introduced in Java 1.5) where possible to catch errors at compile time versus runtime.
- In cases where you are concerned about thread safety, consider using the collections in java.util.concurrent (added in Java 1.5). These often allow you to remove synchronized blocks entirely from code, but the semantics are sometimes a little different (addAll() not threadsafe, adding null to a map is not allowed, etc.).
Object Creation¶
- Avoid the use of resource pools (object pools). The modern VMs have special ways of handling memory that created and quickly dereferenced. Creating your own resource pools fights that mechanism, and more often than not, results in memory leaks.
- Reduce the number of temporary objects being used, especially in loops.
- Design to accept reusable objects to be filled in rather than return new objects in methods.
- Create or use specific classes that handle primitive data types rather than wrapping the primitive data types (accept 'int' not 'Integer').
- Use primitive data types instead of objects as instance variables (use int not Integer).
- Keep constructors simple and inheritance hierarchies shallow.
- Create copies of simple arrays faster by initializing them; create copies of complex arrays faster by cloning them.
Synchronization¶
- Design asynchronous algorithms and approaches whenever possible to minimize overhead associated with synchronous approaches. Typically synchronization is done to protect a resource, which often is a Collection. Try using the java.util.concurrent Collections in situations where you find yourself adding sync locks around blocks to protect collections.
- Minimize synchronization to the smallest most efficient scope.
- Don’t use synchronization in read-only or single-threaded queries.
Network Performance¶
- Design objects so that the minimal amount of information is transported across the wiret.