Monday, April 16, 2007

Competition is healthy! Prototype Spring Bean Creation: Faster than ever before ..

In my last post I had mentioned about some performance benchmarks of Guice and Spring. In one of the applications which I had ported from Spring to Guice, I had an instance of a lookup-method injection, where a singleton service bean contained a prototype bean that needed to be looked up from the context. Here is the sample configuration XML :


<bean id="trade"
  class="org.dg.misc.Trade"
  scope="prototype">
</bean>

<bean id="abstractTradingService"
  class="org.dg.misc.AbstractTradingService"
  lazy-init="true">
  <lookup-method name="getTrade" bean="trade"/>
</bean>



I ran a performance benchmark suite to exercise 10,000 gets of the prototype bean :


BeanFactory factory = new XmlBeanFactory(
    new ClassPathResource("trade_context.xml"));

ITradingService ts = (ITradingService) factory.getBean(
    "abstractTradingService");
StopWatch stopWatch = new StopWatch();
stopWatch.start("lookupDemo");

for (int x = 0; x < 10000; x++) {
  ITrade trade = ts.getTrade();
  trade.calculateValue(null, null);
}
stopWatch.stop();

System.out.println("10000 gets took " +
    stopWatch.getTotalTimeMillis() + " ms");



Spring 2.0.2 reported a timing of 359 milliseconds for the 10,000 gets. I performed the same exercise in Guice with a similar configuration :


public class TradeModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(ITrade.class).to(Trade.class);
    bind(ITradingService.class).to(TradingService.class).in(Scopes.SINGLETON);
  }
}



and the corresponding test harness :


Injector injector = Guice.createInjector(new TradeModule());

long start = System.currentTimeMillis();
for(int i=0; i < 10000; ++i) {
  injector.getInstance(ITradingService.class).doTrade(injector.getInstance(ITrade.class));
}
long stop = System.currentTimeMillis();
System.out.println("10000 gets took " + (stop - start) + " ms");



For this exercise of 10,000 gets, Guice reported a timing of staggering 31 milliseconds.

Then a couple of days back Juergen Hoeller posted in the release news for Spring 2.0.4, that repeated creation of prototype bean instances has improved up to 12 times faster in this release. I decided to run the benchmark once again after a drop-in replacement of 2.0.2 jars by 2.0.4 ones. And voila ! Indeed there is a significant improvement in the figures. The same test harness now takes 109 milliseconds on the 2.0.4 jars. Looking at the changelog, you will notice several lineitems that have been addressed as part of improving bean instantiation timings.

This is what competition does even for the best .. Keep it up Spring guys ! Spring rocks !

Updated: Have a look at the comments by Bob and the followups for some more staggering benchmark results.

6 comments:

Staff Software Engineer at Google said...

Creating a competing product in a space where there already is a clear top dog is "crazy" :)

afsina said...

As long as spring stucks to Java 1.3 compatibiltiy, it will keep sucking.

Staff Software Engineer at Google said...

@A.A.A. I respectfully disagree. Spring can support 1.3 and still support plenty of JDK 5.0 features :)

Bob said...

Your Guice example actually isn't equivalent to your Spring example. Based on the code you've provided, it will be more fair to replace ITradingService with Provider<ITrade> in the Guice example.

Your configuration code should look like this:

  public class TradeModule extends AbstractModule {
    @Override
    protected void configure() {
      bind(ITrade.class).to(Trade.class);
    }
  }

And the test harness should look like this:

  Injector injector = Guice.createInjector(new TradeModule());
  Provider<ITrade> tradeProvider
    = injector.getProvider(ITrade.class);

  long start = System.currentTimeMillis();
  for(int i=0; i < 10000; ++i) {
    ITrade trade = tradeProvider.get();
    trade.calculateValue(null, null);
  }
  long stop = System.currentTimeMillis();
  System.out.println("10000 gets took " + (stop - start) + " ms");

If you want to keep ITradingService around, you can just inject Provider<Trade> into it.

Please let us know the new results.

Unknown said...

Hi Bob -

Thanks a lot for your suggestions. I made the following changes :

public class TradingService implements ITradingService {
  @Inject
  private Provider<ITrade> tradeProvider;

  public ITrade getTrade() {
    return tradeProvider.get();
  }
  // ..
}

and the test harness ..

Injector injector = Guice.createInjector(new TradeModule());
ITradingService ts = injector.getInstance(ITradingService.class);

long start = System.currentTimeMillis();
for(int i=0; i < 10000; ++i) {
  ITrade trade = ts.getTrade();
  trade.calculateValue(null, null);
}
long stop = System.currentTimeMillis();
System.out.println("10000 gets took " + (stop - start) + " ms");

and guess what happened !! The time reported is 16 ms. Previously it was 31 ms. But I realized that this will be a more proper benchmark than the previous one. Thanks for the suggestion and thanks a ton for Guice.

Just one point I would like to clarify from you - what is your opinion regarding annotations like @Inject coming from an external framework (I mean non-JDK) being inserted into business class ?

Thanks.
- Debasish

Bob said...

Early adopters depend on 3rd party frameworks all the time. Before JPA, we depended on Hibernate (you still have to to some extent). There's Joda time. Quartz. Log4j. With Spring, you write lots of XML which is Spring-specific.