Performance impact of using CDI
Asked Answered
M

1

10

I am writing a Java EE 6 web application and I am noticing a significant performance impact when using an injected object versus creating and using the object directly. The overhead appears to be of the order of 50 - 60ms per method call.

For example, using non-injected 150 method calls take approx 500ms whereas using the injected object 150 method calls take 12,000 - 13,000ms. An order of magnitude difference and then some.

Is this usual?

I am running on JBoss AS 7.1.1 final which uses Weld to handle CDI.

The injected object is defined as a singleton bean (via the javax.ejb.Singleton annotation). Could this be causing part of the problem? Or is it just the Weld proxy causing the slow down?

Milagrosmilam answered 21/11, 2012 at 16:0 Comment(15)
If you care about performance that much, you're way screwed by using Java EE to begin with. I seriously doubt proxied interceptors are going to be the bottleneck in your code. That said, what I'd do is put a breakpoint in the debugger inside the intercepted method call to look how many layers of proxies it has to pass through – it's possible you have some configuration issue that causes an excessive amount of them to be applied.Discrepancy
Changing the injected object to be ApplicationScoped rather than @Singleton sped things up by an order of magnitude. I have no idea why and would be interested if anyone has any feedback on this.Milagrosmilam
That's... odd. I'd still poke at it in the debugger to see what the difference in the call chains is. Otherwise we're stuck to guessing the cause of one vague symptom. Generally I believe the cause of this overhead you see should be AOP, but it's a guess more than anything.Discrepancy
@Discrepancy so what alternative to JEE do you have in mindHypo
@Pangea What I had in mind was "not worrying about microbenchmark results in a platform that already has tons of overhead up the wazoo elsewhere, and spends oodles of time waiting on a database or network latency", not an alternative. That said, dotnetland is more or less equivalent, yet more focused on doing things at compile-time, not with reflection-based dynamism. If AOP interceptors is the cause of the overhead, the Spring ecosystem is also an option – it allows for compile-time or load-time AOP weaving, which is much faster; and possibly more control over when AOP is used.Discrepancy
@Discrepancy so cdi has no compile time or load-time weaving?Hypo
@Pangea I've never heard of such a beast, and I can't make Google turn anything up. Spring's AOP is based off of AspectJ, which started out as a build-time tool, so I know for certain that it allows for that. (Though the general groupthink – even in Springland – seems to be that JDK/CGLib proxies are "good enough".) And, to reiterate, this would only help if the problem was that only required interceptors are applied to the proxied object, and they still cause unacceptable overhead – the OP hasn't done nearly enough diagnosis to let me conclude that's the case though.Discrepancy
@Discrepancy - my question actually was to understand the CDI better. Are you saying that CDI doesn't let us use load-time weaving and always uses dynamic proxy?Hypo
@Pangea I'm assuming Weld doesn't, because proxy-based AOP is easier to implement than injecting code into existing methods. I can't really find any documentation on Weld internals that would say how it implements AOP; I also can't find any documentation stating that you can configure this. This leaves only the options of diving into Weld sources, or setting up a scratchpad project to check for proxies in a debugger. (Load-time-weaving modifies your classes directly, so the call stack shouldn't show a proxy class between the caller and implementation.) I'm kind of too lazy to do either.Discrepancy
Just to clarify I was using the javax.ejb.Singleton. I switched to using javax.inject.Singleton and saw the same speed up I got when moving to javax.enterprise.context.ApplicationScoped. So it looks like the EJB version carries additional overhead. Which would make some kind of intuitive sense as javax.ejb.Singleton provides all the features of an EJB such as transaction management.Milagrosmilam
have you tried using a profiler to track down where time is consumed?Intemerate
@AllenParslow - we have extensive logging weaved in using interceptors. This is pin-pointing that time is being consumed specifically within the method of the injected object. Changing the type of injected bean from javax.ejb.Singleton to javax.enterprise.context.ApplicationScoped or javax.inject.Singleton gave order of magnitude performance improvements in calling this specific method. My guess is that EJB singletons have additional overhead due to the enterprise features they provide. Any pointers to documentation regarding which annotation to use and why would be appreciated. ThanksMilagrosmilam
The Singleton from javax.inject is pretty worthless. Either use the EJB one if you need all the features of EJBs, otherwise use the j.e.c one. Also, yes, Weld and OWB use proxies. AS 7.1.1 uses Weld 1.1.5, 1.1.9 I believe, maybe it's 1.18 though is out and includes some additional perf improvements. As for Spring perf, Mark Struberg has found CDI (in both Weld and OWB) to be faster.Morbilli
@Morbilli when you say "otherwise use the j.e.c one" I presume you mean one under the package javax.enterprise.context? If so I cannot find a class called Singleton there. I can however find ApplicationScoped which appears to do what we require. ThanksMilagrosmilam
Yep, you have it right. Sorry for the confusion.Morbilli
M
10

After some help from the excellent Weld forum I have discovered that:

By default, singleton session beans are transactional (section 13.3.7 of the EJB 3.1 specification) and require acquisition of an exclusive lock for every business method invocation (sections 4.8.5.4 and 4.8.5.5). In contrast, a javax.inject.Singleton is not transactional and does not support container-managed concurrency (the major consequence being that no locking scheme is implemented by the container).

If you annotate your singleton session bean with @TransactionAttribute(NOT_SUPPORTED) and @Lock(READ), you should see significantly better performance, though there may still be some overhead. If you don't need EJB features, stick with @ApplicationScoped (javax.inject.Singleton is not defined by CDI, and its semantics are therefore not governed by that specification).

https://community.jboss.org/thread/213684?tstart=0

Sadly even after annotating my EJB singleton with @TransactionAttribute(NOT_SUPPORTED) and @Lock(READ) the performance was still very poor (see timings from original post).

So the take home message is don't inject EJB Singleton session beans unless you absolutely have to and even then be aware of the performance overhead that is likely to be incurred. For methods that are rarely invoked it could be negligible but as in our case small overheads accumulate rapidly if it is a heavily used method.

We did not need the EJB features and on switching to ApplicationScoped saw order of magnitude improvements in performance of the specific method that called through to the injected bean.

Milagrosmilam answered 22/11, 2012 at 8:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.