Java performant code tips

First of all I would like to say that this is NOT a comprehensive guide to java performance testing. This is my thoughts after I did some performance testing on GeDA 2.0.3. However it does have some useful tips (well at least I hope it does).

What approach I have taken?

Well it is quite simple - it all starts with a simple JUnit test that runs some piece of code. Do not make it too complex just a small routine. For GeDA it was a simple single Entity to DTO object data transfer. Next step is basically to wrap it in a huge loop, say 1 billion iterations. The reason for it being so big it that we need some time to examine what is going on when the code runs - which means inspecting memory, call stacks, method hot spots and so forth using a profiling tool. So 1 billion iterations should give us enough time to sneak peak under the hood.

For this performance test I have used JProfiler purely because it has lots of statistics and easily integrates with IDE (I use Intellij IDEA).

So once you wrapped the test in loop - let it rip and keep your eyes on the profiler. The most obvious places to look is memory consumption (for leak) and hot spots (methods that consume most of your CPU time).


So what have I learnt?


Evil no. 1: String concatenation.

Well you may say it is obvious but nothing could be further from the truth.
Event a simple ("mytext" + myVar) buried somewhere deep in your code can easily consume 3% of your CPU time.

Consider the following example:

  private Collection newCollection(final Class< ? extends Collection> clazz, final String type) 
		throws UnableToCreateInstanceException {
    return newBeanForClass(clazz, "Unable to create collection: " + clazz.getCanonicalName() + " for " + type);
  }
  ...
  private T newBeanForClass(final Class clazz, final String errMsg) throws UnableToCreateInstanceException {
  	try {
  		return clazz.newInstance();
  	} catch (Exception iex) {
  		throw new UnableToCreateInstanceException(clazz.getCanonicalName(), errMsg, iex);
  	}
  }

The above method calls a generic bean factory method with an error message as a second parameter. Harmless when coding but consuming 7% of CPU.
We do not need the error message until we actually unable to create that bean, so changing this to:

  private Collection newCollection(final Class< ? extends Collection> clazz, final String type, final String field) 
		throws UnableToCreateInstanceException {
  	return newBeanForClass(clazz, "Unable to create collection: {0} for {1} {2}", clazz, type, field);
  }
  ...
  private T newBeanForClass(final Class clazz, final String errMsg, final Object ... msgParams) 
		throws UnableToCreateInstanceException {
  	try {
  		return clazz.newInstance();
  	} catch (Exception iex) {
  		throw new UnableToCreateInstanceException(clazz.getCanonicalName(),
  					String.format(errMsg, msgParams), iex);
  	}
  }
  

gives us performance gain of 7% - banker would go mad for this kind of percentage gain.

So the lesson no.1: do not concatenate strings until you need them concatenated.

You might think - "Well I am just going to use StringBuilder instead!".
No you won't! because java compiler already does that in the background, so no performance gain will occur.

Evil no.2: Cacheing

Well, not cacheing in itself is evil - but rather how you do this.
In essence cache is just a hash map, key value pairs so that we can retrieve expensive object by key rather than constructing them. So the real issue here - how to generate the right key?

You are ok if you have simple keys, but what if you do not and you need to make them from the parameters that you have?

GeDA 2.0.2 had string keys for some of the expensive objects (assemblers if you need to ask) and each of those was dependent on two class object and an id (synthesizer type if you are being curious again :) ). So the obvious and simple choice was to concatenate some string (remember evil no.1?). Yes, that key had to be concatenated each time you want to access cache, and yes it was expensive. But what can we do - we need that complex key right? Hash code to the rescue!

The previous implementation had:

  private static String createAssemberKey(final Class dto, final Class entity, final MethodSynthesizer synthesizer) {
  
  	final StringBuilder key = new StringBuilder(dto.getCanonicalName());
  	key.append('-');
  	key.append(entity.getCanonicalName()).append('-');
  	key.append(synthesizer.toString());
  	return key.toString();
  }
  

Well if we are using hashMap and the address of the assembler in hashMap will be derived from key.hashCode() why not just create a hash code in the first place?

  private static Integer createAssemberKey(final Class dto, final Class entity, final Object synthesizer) {
  	int result = dto.hashCode();
  	result = 31 * result + entity.hashCode();
  	result = 31 * result + synthesizer.hashCode();
  	return Integer.valueOf(result);
  }
  

This give a boost of 5% on CPU time.
And lesson no.2: Choose you cache keys wisely, you may find that looking up keys sometimes more expensive then creating the object itself.

Evil no.3: Regular expressions

Regular expressions are great but they are quite expensive. Of course there are times when you just need to use them, but there are occasions when you can bypass this step. Say you know that there is a limited amount of checks coming in but these check need to be done every time. Like for example in GeDA some class names are blacklisted by specifying a setting using regular expression to provide maximum flexibility. We know that there is a finite number of Entities in the system - I mean event in the most complex systems you will rarely see more than 200. So even if we check all of them - it will be up to 200 entries to check. In our test we do a single entity check (since it is just one class we are transferring data from) but we do it 1 billion times, so the regex runs 1 billion times. Now if we create a "whitelist" cache we only need 1 check.

So the code was:

  private static Class filterBlacklisted(final Class className) {
  	if (matches(className.getSimpleName())) {
  		if (!className.getSuperclass().equals(Object.class)) {
  			// some proxies are derived straight from Object.class - we do not want those
  			return filterBlacklisted(className.getSuperclass());
  		}
  	}
  	return className;
  }
  

And now we change it to:

  private static Class filterBlacklisted(final Class className) {
  	final int hash = className.hashCode();
  	if (!WHITELIST_ENTITIES.contains(hash)) {
  		if (matches(className.getSimpleName())) {
  			if (!className.getSuperclass().equals(Object.class)) {
  				// some proxies are derived straight from Object.class - we do not want those
  				return filterBlacklisted(className.getSuperclass());
  			}
  		} else {
  			WHITELIST_ENTITIES.add(hash);
  		}
  	}
  	return className;
  }
  

Our WHITELIST_ENTITIES hash set contain hashCodes of classes that where white listed (and this set will never be more than 200). Hash set ensures quick access and containing just Integer object this is quite lightweight on memory.

Lesson no.3: Bypass regex if you can.



Evil no.4: Unnecessary object instantiations


These are not so obvious things in your code and that is why you definitely need a profiling tool.
Say your code needs to create an instance of an object but then it only uses it under certain conditions.

  final MethodSynthesizer syn = synthesizer == null ? SYNTHESIZER : new MethodSynthesizerProxy(synthesizer);
  final String key = createAssemberKey(dto, realEntity, syn);
  
  Assembler assembler = CACHE.get(key);
  if (assembler == null) {
  	assembler = new DTOtoEntityAssemblerImpl(dto, realEntity, syn);
  	CACHE.put(key, assembler);
  }
  return assembler;
  

Now it is quite obvious that variable syn will only be used if we do not have assemble in cache, but the MethodSynthesizerProxy will be created regardless.

Rearranging the code sligthly:

  
  if (synthesizer == null) {
  	key = createAssemberKey(dto, realEntity, SYNTHESIZER);
  } else {
  	key = createAssemberKey(dto, realEntity, synthesizer);
  }
  
  Assembler assembler = CACHE.get(key);
  if (assembler == null) {
  	final MethodSynthesizer syn;
  	if (synthesizer == null) {
  		syn = SYNTHESIZER;
  	} else {
  		syn = new MethodSynthesizerProxy(synthesizer);
  	}
  	assembler = new DTOtoEntityAssemblerImpl(dto, realEntity, syn);
  	CACHE.put(key, assembler);
  }
  return assembler;
  

And we just got rid of 1 billion object instantiation in our performance test.

Lesson no.4: Sometimes making code look prettier incurs performance losses. Make sure this does not happen.


Hope you enjoyed my article and my final though is: Performant code is not pretty, so do not chase beauty in coding - make sensible decisions to make it robust and performant!





This page was last updated on: 20/09/2012 05:48