JAW Speak

Jonathan Andrew Wolter

Spring Slow Autowiring by Type getBeanNamesForType fix 10x Speed Boost >3600ms to <100ms

with 9 comments

Reading time: 4 – 6 minutes

We’re using Spring MVC, configured mostly by annotations, a custom scope for FactoryBeans so they don’t get created once per request, and autowiring by type. This makes for simple code, where the configuration lives right along where it is configured, however after a certain number of autowired beans, performance was abysmal.

Loading the main front page in Firefox, with firebug showing slowness:
baseline-firebug

Click on to read the full post.

My 8 core box was taking >3000ms to load the /home/ page and after these changes went to <300 ms. (This was in a configuration isolate from any backend service calls or databases – it used stub data, so should be very fast). Before it couldn’t handle over 2 concurrent requests, after we were doing 2 concurrent requests with 70 ms max response times. (Under 300ms for 8-16 concurrent requests).

How did I do this? I discovered it via profiling the app. I was surprised at so much activity in getting the classloader.
baseline-profiling

Drilling down further, I could see where Spring was calling over and over two slow methods in getBeanNamesForType: isFactoryBean and isTypeMatch.
baseline-profiling-drill-down

It is through caching some metadata spring was otherwise recalculating. Every time Spring autowired something, spring iterated over every bean and did reflection to learn it’s type and if it was a factory bean. Spring is very slow when autowiring by type. My colleague (and Spring Batch co-author) Lucas Ward explained SpringSource discouraged autowiring by type until 2.5 came out, and they reversed their position due to the elegant annotation driven web controller configurations. However, you may experience a performance bottleneck. (The only related bug logged that I found was here). I’ve worked with Guice a few years back, and all of this metadata was cached there. I was astonished it wasn’t cached by Spring. I’ll explain how I added my own layer of caching, making an order of magnitude improvement in our system with several hundred beans:

Code:

Here’s the same load under profiling, after adding the caching.
after-caching-profiling

And here it is in firebug:
after-caching-firebug

What were the raw numbers for speedup? I was running apache bench to cause load. Here were before and after results. 2 concurrent requests, 50 requests. ab -t180 -n$NUM_REQ -c$CONCURRENT -r -e $FILE_BASE.csv -g $FILE_BASE.dat http://local.example.com:8080/blah/ > $FILE_BASE.txt

Before:

Server Software:        Apache-Coyote/1.1
Server Hostname:        local.example.com
Server Port:            80

Document Path:          /blah/
Document Length:        123965 bytes

Concurrency Level:      2
Time taken for tests:   89.509 seconds
Complete requests:      50
Failed requests:        49
   (Connect: 0, Receive: 0, Length: 49, Exceptions: 0)
Write errors:           0
Total transferred:      6198170 bytes
HTML transferred:       6177670 bytes
Requests per second:    0.56 [#/sec] (mean)
Time per request:       3580.374 [ms] (mean)
Time per request:       1790.187 [ms] (mean, across all concurrent requests)
Transfer rate:          67.62 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:  3500 3580  50.4   3579    3681
Waiting:     3490 3573  50.6   3571    3675
Total:       3500 3580  50.4   3579    3681

Percentage of the requests served within a certain time (ms)
  50%   3579
  66%   3611
  75%   3614
  80%   3618
  90%   3654
  95%   3668
  98%   3681
  99%   3681
 100%   3681 (longest request)

After:

Server Software:        Apache-Coyote/1.1
Server Hostname:        local.example.com
Server Port:            8080

Document Path:          /blah/
Document Length:        83302 bytes

Concurrency Level:      2
Time taken for tests:   1.141 seconds
Complete requests:      50
Failed requests:        48
   (Connect: 0, Receive: 0, Length: 48, Exceptions: 0)
Write errors:           0
Total transferred:      4184702 bytes
HTML transferred:       4165052 bytes
Requests per second:    43.81 [#/sec] (mean)
Time per request:       45.654 [ms] (mean)
Time per request:       22.827 [ms] (mean, across all concurrent requests)
Transfer rate:          3580.48 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:    40   45   6.3     43      70
Waiting:       38   43   6.3     41      69
Total:         40   45   6.3     43      70

Percentage of the requests served within a certain time (ms)
  50%     43
  66%     44
  75%     45
  80%     48
  90%     54
  95%     55
  98%     70
  99%     70
 100%     70 (longest request)

I didn’t find much else about this for Spring’s slowness in Autowire by type. Please let me know if you find this helpful. Thanks.

Bookmark and Share

Written by Jonathan

November 28th, 2010 at 4:25 am

Posted in code, java

Tagged with

9 Responses to 'Spring Slow Autowiring by Type getBeanNamesForType fix 10x Speed Boost >3600ms to <100ms'

Subscribe to comments with RSS or TrackBack to 'Spring Slow Autowiring by Type getBeanNamesForType fix 10x Speed Boost >3600ms to <100ms'.

  1. [...] This post was mentioned on Twitter by Stéphane Nicoll. Stéphane Nicoll said: RT @olamy: spring optimization http://bit.ly/hHc3aJ interesting ! [...]

  2. Nice metrics you show here; however I don’t understand why your app has to autowire stuff when loading the home page… Shouldn’t your dependencies be set, once and for all, right after the application context is initialized?
    Are you explicitly invoking the getBeanNamesForType method? AFAIK your caching mechanism should only improve the app deployment time…

    Vincent

    23 Sep 11 at 10:34 am

  3. We had request scoped beans, so they were instantiated every request.

    Jonathan

    23 Sep 11 at 11:26 am

  4. take a look at https://github.com/krosenvold/convention-bean-factory, which is 3x faster than regular spring for this

    krosenvold

    6 Oct 11 at 4:02 am

  5. Is it necessary to always return new CachingByTypeBeanFactory() in CachingWebApplicationContext ? Would it not be enough to create one instance and serve it all the time?

    mindas

    23 Feb 12 at 10:25 am

  6. Well done – I’ll give it a try.

    sorencito

    9 Jul 12 at 1:22 pm

  7. [...] BeanFactory with SpringJUnit4ClassRunner? The slow autowire by type problem has finally been solved by creating a caching bean [...]

  8. [...] SPR-6870 which has high number of votes but for some reason isn’t addressed. This led me to an attempt to solve this problem which does significantly improve the situation but is still slower ~25 times than lookup by String! [...]

  9. queen elsa costume…

    Spring Slow Autowiring by Type getBeanNamesForType fix 10x Speed Boost >3600ms to at JAW Speak…

Leave a Reply