Sunday, April 12, 2009

Java Reflection Performance Costs

Most of the Java Programmers are reluctant to use Java Reflection methodology in their programming citing performance implication. But when actually measuring out the performance costs, I found out that the time difference was rather negligible.

Let us see by example what is the performance cost involved in Java Reflection using some examples such as looking up a method by name and then invoking it.

---
Test Code
Object object = new Object();
Class c = Object.class;

int loops = 100000;
long start = System.currentTimeMillis();
for( int i = 0; i < loops; i++ )
{
object.toString();
}
System.out.println( loops + " regular method calls:" + (System.currentTimeMillis() - start) + " milliseconds." );

java.lang.reflect.Method method = c.getMethod( "toString", null );

start = System.currentTimeMillis();
for( int i = 0; i < loops; i++ )
{
method.invoke( object, null );
}

System.out.println( loops + " reflective method calls without lookup:" + (System.currentTimeMillis() - start) + " milliseconds." );

start = System.currentTimeMillis();
for( int i = 0; i < loops; i++ )
{
method = c.getMethod( "toString", null );
method.invoke( object, null );
}

System.out.println( loops + " reflective method calls with lookup:" + (System.currentTimeMillis() - start) + " milliseconds." );

// Now use HashMap lookup
// Add some data into HashMap

HashMap methodNameHandlerMap = new HashMap();

methodNameHandlerMap.put("toString", c.getMethod("toString", null));
methodNameHandlerMap.put("toString1", c.getMethod("toString", null));
methodNameHandlerMap.put("toString2", c.getMethod("toString", null));
methodNameHandlerMap.put("toString3", c.getMethod("toString", null));
methodNameHandlerMap.put("toString4", c.getMethod("toString", null));
methodNameHandlerMap.put("toString5", c.getMethod("toString", null));
methodNameHandlerMap.put("toString6", c.getMethod("toString", null));
methodNameHandlerMap.put("toString7", c.getMethod("toString", null));
methodNameHandlerMap.put("toString8", c.getMethod("toString", null));
methodNameHandlerMap.put("toString9", c.getMethod("toString", null));
methodNameHandlerMap.put("toString10", c.getMethod("toString", null));
methodNameHandlerMap.put("toString11", c.getMethod("toString", null));
methodNameHandlerMap.put("toString21", c.getMethod("toString", null));
methodNameHandlerMap.put("toString32", c.getMethod("toString", null));
methodNameHandlerMap.put("toString42", c.getMethod("toString", null));
methodNameHandlerMap.put("toString52", c.getMethod("toString", null));

start = System.currentTimeMillis();
for( int i = 0; i < loops; i++ )
{
methodNameHandlerMap.get("toString").invoke( object, null );
}

System.out.println( loops + " reflective method calls with hashmap lookup:" + (System.currentTimeMillis() - start) + " milliseconds." );

---------

In the above code, I am calling toString method in 4 different ways.

Direct call to method.
Invocation using java reflection.
Look up and invocation using java reflection.
Look up from an HashMap and invocation using java reflection.


The Results were as below.
-----

100000 regular method calls:94 milliseconds.
100000 reflective method calls without lookup:109 milliseconds.
100000 reflective method calls with lookup:453 milliseconds.
100000 reflective method calls with hashmap lookup:110 milliseconds.

-----

From the results it is quite clear that the look up is costly but the actual method invocation is quite cheap. There is hardly any time difference (considering a million calls) between the regular method calls and reflective method calls without lookup or using HashMap lookup.

So, for achieving cleaner code and better performance, we can go for HashMap lookups which stores the looked up method and uses it wherever required.

Obviously, if the execution time of the method is relatively large, the invocation overhead becomes negligible, at which point consideration of the cost of complexity overhead of the reflective code versus the amount of flexibility/code reduction becomes the primary concern.

( The tests were run using a Standard P4 Windows XP machine with jdk 1.5.0_07)