2008-02-23

Performance Overhead of Non-Final Method Calls in Java

Working with developers who care deeply about application performance, you get around to having frequent interesting discussions about the subject. We end up reviewing every frequent temporary object allocation, scrutinizing every usage of a mutex in the fast-path for possible elimination, etc. Recently the topic was something really simple on the surface: people are afraid to call non-final instance methods, especially in situations where the method being called needs to be resolved to a particular superclass. Is this fear justified?

There is evidence and wisdom out there pointing to a non-trivial performance penalty of non-final method calls: articles like this one (PDF), my boss' repeated assertions, and the Java Platform Performance book, if I recall correctly.

Unfortunately (or fortunately?) I couldn't find, in a simple test, evidence to support these fears. I designed a test that performed a warm-up (to allow HotSpot to compile what it could after seeing how often I was calling specific methods), then used a System.nanoTime() call right before and after a ten-million iteration loop that just calls an "increment a counter" instance method on an object.

Test Cases

java_perf 

  • There is a warmup period before any timing loop (it's also 10,000,000 calls).
  • I'm not doing any object allocation in the timing loop, so the GC won't run and screw up our results.
  • I ran the whole test suite 25 times and averaged the results.
  • Just to be extra safe, I'm getting the counter value at the end of the 10,000,000-call loop to prevent any 'cleverness' from the compiler determining the end result is useless and not doing the call at all.
  • The first two tests deal with trying a final counter incrementing method in SingleClass, as well as a non-final counter incrementing method. SingleClass is declared as final and cannot be subclassed.
  • The second two test cases do the same as the first two, but calling all methods on SubClass.

The Results

CropperCapture[213]

The conclusion seems to be: it doesn't matter at all what you do. I see a few explanations:

  • The common wisdom of there being a significant overhead to non-final method invocations may have been true in previous versions of the JVM, and modern JVMs may have levelled the playing field.
  • My test was flawed in some way. (Doing a counter++ was so simple it got inlined?)

No comments: