Performance of Reflection
Our discussion of caching constraint maps leads to the question of reflection's performance. I am frequently asked how expensive reflection is in terms of CPU and memory. The answer is that reflection can be cheap or expensive depending on how you use it. No tool in any coding language has zero cost. Example 9-6 shows how reflection performs.
Example 9-6. Measuring reflection's performance
package oracle.hcj.reflection;
public class ReflexiveInvocation {
/** Holds value of property value. */
private String value = "some value";
public ReflexiveInvocation( ) {
}
public static void main(final String[] args) {
try {
final int CALL_AMOUNT = 1000000;
final ReflexiveInvocation ri = new ReflexiveInvocation( );
int idx = 0;
// Call the method without using reflection.
long millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
ri.getValue( );
}
System.out.println("Calling method " + CALL_AMOUNT
+ " times programatically took "
+ (System.currentTimeMillis( ) - millis) + " millis");
// Call while looking up the method at each iteration.
Method md = null;
millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
md = ri.getClass( )
.getMethod("getValue", null);
md.invoke(ri, null);
}
System.out.println("Calling method " + CALL_AMOUNT
+ " times reflexively with lookup took "
+ (System.currentTimeMillis( ) - millis) + " millis");
// Call using a cache of the method.
md = ri.getClass( )
.getMethod("getValue", null);
millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
md.invoke(ri, null);
}
System.out.println("Calling method " + CALL_AMOUNT
+ " times reflexively with cache took "
+ (System.currentTimeMillis( ) - millis) + " millis");
} catch (final NoSuchMethodException ex) {
throw new RuntimeException(ex);
} catch (final InvocationTargetException ex) {
throw new RuntimeException(ex);
} catch (final IllegalAccessException ex) {
throw new RuntimeException(ex);
} }
public String getValue( ) {
return this.value;
} }
In the first part of this test, you measure the time it takes to call the getter of the class normally 1 million times. In the second phase, you measure the time it takes to call the getter 1 million times using a reflection-based invocation, in which the getter method is looked up each time the loop is iterated. Finally, you cache the lookup of the getter and invoke the getter method 1 million times using the cached Method object. The results of running this class are shown here:
>ant -Dexample=oracle.hcj.reflection.ReflexiveInvocation run_example run_example:
[java] Calling method 1000000 times normally took 20 millis
[java] Calling method 1000000 times reflexively with lookup took 5618 millis
[java] Calling method 1000000 times reflexively with cache took 270 millis
 |
Your times may vary depending on the capabilities of your computer, but generally, the ratios will be the same.
|
|
The phase during which the Method object was cached was significantly faster than the phase during which the Method object was looked up at each iteration. Naturally, both phases were slower than invoking the method normally. This test teaches a couple of lessons. First, looking up the Method object is the most expensive part of reflection. If you cache the method, you can dramatically improve performance. Second, reflection is slower than invoking methods normally. However, this performance loss is compensated with the benefit of producing your products significantly faster and with fewer bugs. In most cases, the benefits of reflection are well worth the cost of 0.25 seconds over 1 million calls.
|