Skip to main content

Compile- vs load-time weaving performance in Spring

Yesterday I had pleasure to participate in Java Developers’ Day in Kraków, Poland. It was a great experience to see Mark Richards (author of Java Message Service) and Scott Davis (Groovy Recipes) giving a talk. Also I really enjoyed Wojciech Seliga speak about code review. He works for Atlassian and shown a bit of Crucible, but his main point was that code review is not about looking for bugs made by other developers. It is rather an agile process of getting to know the code.

I could write much more about JDD, of course starting from "you should regret if you haven’t been there", but I am quite sure that you are already waiting for the main topic. Let’s just say, that there is a chance that JDD will take 2 days in the next year and I will do my best to be there.


After reading my previous post one of my friends asked about performance of creating objects marked as @Configurable. He wants to inject EntityManager or other custom dependencies to his JPA POJOs but is concerned about performance. Because many thousands of objects are created manually or by Hibernate during the application life, overhead introduced by Spring aspect injecting dependencies each time new operator is called may be significant. There is no sense in talking about performance, I will simply measure everything!

But before we start our performance comparison test: I haven’t yet explained how to enable compile-time weaving instead of load-time. First a word of explanation: CTW weaves aspect during compilation phase when building your application using Maven. LTW does that when the class is loaded within the JVM. Because both approaches should weave the same aspect and produce the same code, true performance of object creation should be the same despite the weaving method. But we will check that out too.

As you probably expect, switching from LTW to CTW is only a matter of configuration, no code must be changed. All you need to do is remove LTW Spring agent from pom.xml (surefire plugin configuration) and references to the agent when running JVM (on server and unit tests from your IDE). When you got rid of LTW, enable CTW in no time (pom.xml):

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<configuration>
<complianceLevel>1.6</complianceLevel>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>



Nothing to be explained, maybe except the complianceLevel, which corresponds to your JDK version. You must also replace:

<context:load-time-weaver/>


with:

<context:spring-configured/>


In your Spring context configuration file.

Now, when we know how to switch back and forth from LTW to CTW, let’s run some performance tests. I used Reservation class introduced in previous posts for testing creation performance, but I added three dependencies via transient fields in this POJO:


@PersistenceContext
private transient EntityManager em;

private transient TicketService ticketService;

@Resource
private transient JavaMailSender javaMailSender;


The TicketService is being injected using the configuration below:

<bean class="com.blogspot.nurkiewicz.reservations.Reservation" scope="prototype" lazy-init="true">
<property name="ticketService" ref="ticketService"/>
</bean>


By testing three beans with different injection techniques I wanted to find out, whether the type of injection has different performance impact.

The test scenario was to first create 1000 instances of Reservation class to warm up JVM and then measure the time of creating 50 thousand instances and putting them in previously created array to partially prevent GC. Objects were created in the following weaving environment:

  • none – no weaving has been applied
  • CTW (no dependencies) – CTW enabled and @Configurable annotation added to Reservation class, but no dependencies injected (@PersistenceContext, @Resource and XML configuration removed)
  • CTW (EM) – CTW with EntityManager injected
  • CTW (<property>) – CTW with dependency configured in Spring XML context file being injected
  • CTW (@Resource) – CTW with dependency autowired using standard @Resource annotation
  • CTW (all) – CTW with all three dependencies listed above injected
  • LTW (no dependencies) – like above but using LTW
  • LTW (EM) – like above but using LTW
  • LTW (<property>) – like above but using LTW
  • LTW (@Resource) – like above but using LTW
  • LTW (all) – like above but using LTW

Each test creating 50K instances has been repeated 8 times with very low standard deviation. I measured the time it took JVM to create all the instances and scaled it to the number of instances being created per second. Bigger value is better.

Probably you start to feel bored so I skip detailed results and give you this nice chart:



The results are a bit surprising for me for two reasons. First, creating objects marked as @Configurable is less than 4 times slower than creating ordinary objects using new operator. I assumed that creating new objects on heap is so greatly optimized that adding the overhead of Spring aspect scanning the class and trying to inject dependencies to completely unknown class would be tremendous. But even if single dependency is being injected using @Resource or XML configuration, creation time is reasonable. In fact, 4-5 times slower when the object is created by Hibernate is something that is almost impossible to measure and see in real environment – simply database and network connectivity brings much bigger overheads, making injection time insignificant. Also, just as I thought, there is no big difference between LTW and CTW when there comes to performance, so use whichever you like. Or at least do not take performance into account when deciding.

The second surprise, negative this time, was the time of creating objects with injected EntityManager. Somehow it is almost 30 times slower than normal object creation and 6 times slower than injecting other custom Spring beans. It is not the subject of this post to find out what happens behind the scenes with EntityManager (probably Spring does some additional magic with EntityManager proxy, maybe I will investigate this in the future), but the results are disturbing.

To summarize: using @Configurable annotation and injecting your Spring beans probably won’t be performance issue in your project. Of course my test isn’t definite and you should check this in your particular case, but the benefits of Spring injection into domain objects can’t be overestimated. But be careful when injecting EntityManager directly to your domain objects – the performance impact is somewhat significant and when creating thousands of objects your application might slow a little bit.

Test environment: Intel Core Duo T2050 1.6 GHz, 1 GiB RAM, Windows XP SP2, JDK 1.6.0_14


Comments

  1. Great post !

    I could be too hopeful here, but is it possible that you re-run the tests with newer versions of jdk, aspectj and spring, and share to us whether there have been any significant improvements ?

    Thanks !

    ReplyDelete
  2. Have you done CTW in Spring using Gradle as a build tool? I am struggling to do it myself and the Web got me no answer to that...

    ReplyDelete
  3. With respect to injecting EntityManagers, and why it seems to be so much slower: I think that we should realize that the EM, as being an interface to such a complex thing as a relational DBMS, is on itself quite a complex resource, it cannot be compared to injecting Plain Old Spring Beans.

    Instantiating an EM and binding it to the client might be slower because it actually has to perform several kinds of validation steps, that might need to even go to the database! The annotation itself (@PersistenceContext as far as the Java EE world is concerned) allows for specifying the name of the persistence unit, and the scope of the persistence context (transaction vs. extended). First parameter needs to validate if such a PU name exists. For the second one additional machinery is supposedly needed to ensure proper semantics. Also, with latest JPA development, EM might need to initialize more stuff, especially stuff that is specific for each persistence context, like cache(s).

    Not giving explanation for the observed performance, just thinking out loud. EM is complex shit!

    ReplyDelete

Post a Comment