Tuesday, August 01, 2006

Java Reflection no longer that slow!

[WARNING: Turns out I was wrong about reflection, see the blog post above for my addendum! I'm only leaving the contents here for "historical" reasons! REFLECTION IS SLOW!!!!]

I was in the process of evaluating validation frameworks for Java applications and came across iScreen. Looking at that framework, I liked the fact that it performed a mapping from any given object to a more specific object that the validation required. This eliminated the need to have every object implement some sort of a "IValidatable" interface that would enable some generic validation object to validate it.

For example, if you want to write a generic method that validates all objects that have a start date and an end date, you'd need for every object to have a "getStartDate()" and "getEndDate()" method so that this generic method could perform the validation. So you'd create an "IDateValidatable" interface that has a "getStartDate()" and "getEndDate()" method and force any object that wanted to be validated to implement these methods. This is rather clunky because it means that your data access objects might need to implement an interface that it really doesn't care about at all (most validation happens at the business layer, not the data access layer). Normally, one object needs to be validated in multiple ways, so this same data access object might also need to implement other methods in order to be validatable by other generic methods. This means the one object has to have knowledge of any and all validations that anyone might need to perform on it. Considering that two completely separate groups might be implementing the data access layer and the business layer, coordinating all the validations becomes a mess rather quickly.

iScreen has a different approach. Instead of trying to have interfaces that any and all objects must implement in order to be validated, iScreen allows each generic validator to define an object that contains the methods it needs in order to validate the data, and then iScreen will guarantee that an instance of that object is passed into the validator for validation. For example, a DateValidator might define an object that contains "getStartDate()" and "getEndDate()" methods, and then this object would be what iScreen passes into the DateValidator. iScreen is able to guarantee that this object is passed in because it allows the developer to define a set of rules to map the data from any arbitrary object into an instance of this object. So, if there was an Employee object that had a "getHireDate()" and "getTerminationDate()", iScreen allows the developer to map the "getHireDate()" method to the "getStartDate()" method, and the "getTerminationDate()" method to the "getEndDate()" method. Once that mapping is performed, anytime an Employee is sent in for validation, iScreen automatically creates a new instance of the object that the validator needs and populates it with the data from the Employee object.

It's quite a neat way around the fundamental problem of how to enable objects to be used in multiple generic contexts without requiring them to implement any and all interfaces that might be required across all of the layers of the application. Consider for example the need to display objects in a table. There might be a generic table component that always displays an ID, a name, and a description. Without this mapping functionality, it would be necessary for an interface to be created that had "getID()", "getName()", and "getDescription()" methods, and a data object that comes all the way from the back-end data access layer would be required to implement this interface. This would be extremely cumbersome, not to mention a terrible break in the encapsulation of the different layers of the application, since front-end UI concerns would be infecting the objects in the back-end. Instead, if the data object could be automatically mapped to some other object that the front-end UI component required, this would enable both the creation of the reuseable UI component without requiring the data object to be aware of it. It's really the best of both worlds.

iScreen achieves this mapping capability by using a technology called OGNL. Looking under the hood of OGNL, I was dismayed to see that it appeared to use reflection to perform the mapping. My understanding had always been that reflection was quite slow, so I decided not to use iScreen and OGNL, but instead to write my own mapping tool based on cglib, since it was my understanding that cglib performed byte-code manipulations to achieve faster reflection-like operations. Specifically, I used cglib's FastClass, FastMethod, and Enhancer APIs. (One very frustrating thing about cglib was its lack of documentation. It was obvious to me that there were other APIs that I could be making in order to potentially achieve more speed, but there is almost no documentation for how to use them, so all I could do was use FastClass and FastMethod).

When I was done coding the first part of the data mapping tool and began unit testing it, I decided to do some performance benchmarks to see how much of a speed gain I had achieved using cglib instead of reflection. After all, if the performance difference was minimal, I might as well stop my custom coding and just use OGNL and iScreen. I set up a synthetic microbenchmark that simply called a FastMethod versus a regular reflection Method over and over again and timed the operations. I was quite shocked when I saw the results.






# Iterationscontrolreflectioncglib
10,000x0ms5ms21ms
100,000x4ms17ms105ms
1,000,000x47ms97ms971ms


The control was simply calling a regular method without reflection over and over again. While the control was the fastest of all, the most surprising result was that cglib was much much slower than reflection! Over 10,000 iterations it was about 4x slower, but at 1,000,000 iterations it was almost 10x slower. Now granted, synthetic microbenchmarks are always a little bit of hooey, but these differences aren't small at all. I tried the benchmark both on a WindowsXP Pentium4 class machine and a FreeBSD laptop Pentium M, and in both cases the differences between the two were comparable. So there was basically no question about it, reflection is faster than cglib, at least in the way that I was using cglib, and it didn't make sense to write my own data mapping tool using cglib.

The only question is whether or not it's worth it to take the performance hit of using reflection versus regular methods. Personally, when I see that at 100,000 iterations each reflection method call only took 0.00017ms, or below the nanosecond threshold, and those numbers were even better at 1,000,000 iterations, I'd say the coding problem that's solved by using reflection is worth the minor cost in performance.

0 Comments:

Post a Comment

<< Home