Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance bottlenecks while creating scoped bean instances #30883

Closed
jengebr opened this issue Jul 13, 2023 · 7 comments
Closed

Performance bottlenecks while creating scoped bean instances #30883

jengebr opened this issue Jul 13, 2023 · 7 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: enhancement A general enhancement
Milestone

Comments

@jengebr
Copy link

jengebr commented Jul 13, 2023

Affects: 5.2+

My organization has multiple high-traffic, customer-facing applications that create many beans per request. The individual creations are fast enough but in aggregate these beans contribute noticeably to the request's critical path latency, as well as the per-request cpu. The key portion of the stack trace is:

org.springframework.beans.factory.support.ConstructorResolver.resolvePreparedArguments
org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod
org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance

Eventual leaf nodes of the call graph are:

  • java.lang.Class.forName0
  • java.lang.reflect.Constructor.getParameterAnnotations
  • java.lang.reflect.Method.getParameterAnnotations
  • org.springframework.core.annotation.AnnotationUtils.getValue

Annotations and classes don't change during runtime, yet they are repeatedly processed.


Solution

Some level of caching is appropriate, either multiple caches (one per leaf) or a single larger one around ConstructorResolver.resolvePreparedArguments. Multiple specific caches seems preferable because of lower contention and simpler keys.


Supporting Data

Below are further, detailed stack traces that illustrate the path from ConstructorResolver.resolvePreparedArguments to the leaf nodes.

org.springframework.core.annotation.AnnotationUtils.getValue
org.springframework.core.annotation.AnnotationUtils.getValue
org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver.checkQualifiers
org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver.isAutowireCandidate
org.springframework.beans.factory.support.DefaultListableBeanFactory.isAutowireCandidate
org.springframework.beans.factory.support.DefaultListableBeanFactory.isAutowireCandidate
org.springframework.beans.factory.support.DefaultListableBeanFactory.isAutowireCandidate
org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates
org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency
org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency
org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument
org.springframework.beans.factory.support.ConstructorResolver.resolvePreparedArguments
java.lang.reflect.Constructor.getParameterAnnotations
org.springframework.core.MethodParameter.getParameterAnnotations
org.springframework.beans.factory.InjectionPoint.getAnnotations
org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver.isLazy
org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver.getLazyResolutionProxyIfNecessary
org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency
org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument
org.springframework.beans.factory.support.ConstructorResolver.resolvePreparedArguments
java.lang.reflect.Method.getParameterAnnotations
org.springframework.core.MethodParameter.getParameterAnnotations
org.springframework.beans.factory.InjectionPoint.getAnnotations
org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver.isLazy
org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver.getLazyResolutionProxyIfNecessary
org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency
org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument
org.springframework.beans.factory.support.ConstructorResolver.resolvePreparedArguments
java.lang.Class.forName0
java.lang.Class.forName
org.springframework.util.ClassUtils.forName
org.springframework.beans.factory.config.MethodInvokingBean.resolveClassName
org.springframework.util.MethodInvoker.prepare
org.springframework.beans.factory.config.MethodInvokingFactoryBean.afterPropertiesSet
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.lambda$invokeInitMethods$5
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$Lambda.run
java.security.AccessController.doPrivileged
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean
<proprietary BeanFactory subclass>.createBean
org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$1
org.springframework.beans.factory.support.AbstractBeanFactory$Lambda.getObject
<proprietary request scope subclass>.createBean
<proprietary request scope subclass>.get
org.springframework.beans.factory.support.AbstractBeanFactory.getBean
org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference
org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary
org.springframework.beans.factory.support.ConstructorResolver.resolvePreparedArguments
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jul 13, 2023
@jhoeller jhoeller added in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jul 13, 2023
@jhoeller jhoeller self-assigned this Jul 13, 2023
@jhoeller jhoeller added this to the 6.x Backlog milestone Jul 13, 2023
jengebr added a commit to jengebr/spring-framework that referenced this issue Jul 14, 2023
@jengebr
Copy link
Author

jengebr commented Jul 14, 2023

I created a pull request to address three of the four stacks listed in this issue, which represent 95% of the problem and are sufficient to satisfy this issue.

@jhoeller jhoeller changed the title Performance bottlenecks while creating beans Performance bottlenecks while creating scoped bean instances Jul 14, 2023
@jhoeller jhoeller modified the milestones: 6.x Backlog, 6.0.12 Jul 14, 2023
@jhoeller jhoeller added the for: backport-to-5.3.x Marks an issue as a candidate for backport to 5.3.x label Jul 14, 2023
@github-actions github-actions bot added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-5.3.x Marks an issue as a candidate for backport to 5.3.x labels Jul 14, 2023
@jhoeller
Copy link
Contributor

We cache a ConstructorDependencyDescriptor per autowired constructor or factory method argument now, including shortcut bean retrieval (aligned with AutowiredAnnotationBeanPostProcessor). This should not only address parameter annotations caching across all scoped instances of the same bean but also shortcut even further, going straight via getBean for a pre-resolved target bean. As a minor improvement, this also bypasses a conversion attempt for prepared autowired arguments now.

@jhoeller
Copy link
Contributor

I've also included a minor MethodInvoker optimization for a pre-resolved targetClass, e.g. for targetClass+staticMethod configuration (even if that isn't recommended since targetClass+targetMethod is preferable there).

Last but not least, the checkQualifiers hotspot should disappear as well now since the shortcut bean retrieval (mentioned above) bypasses this for a pre-resolved target bean now, just like it does for @Autowired fields and methods already.

jhoeller added a commit that referenced this issue Jul 15, 2023
Aligned with shortcut handling in AutowiredAnnotationBeanPostProcessor.
Includes minor MethodInvoker optimization for pre-resolved targetClass.

Closes gh-30883

(cherry picked from commit 6183f06)
@jhoeller
Copy link
Contributor

This is in 5.3.x as well now, feel to give it a try! Should be straightforward enough to patch 5.2.x with it but I strongly recommend an upgrade to 5.3.30 (and trying a 5.3.30 build snapshot for the time being).

FWIW the 5.2.x line has reached its end of open source support a while ago already and will reach its ultimate end of life by the end of this year.

jhoeller added a commit that referenced this issue Jul 16, 2023
@jhoeller
Copy link
Contributor

Aside from some alignment in related classes, the core part of the change is in org.springframework.beans.factory.support.ConstructorResolver. Patching any 5.2.x version of the framework with just the latest ConstructorResolver file from the 5.3.x branch should be sufficient to address the annotation hotspots above. That said, I'd appreciate a test against the actual latest 5.3.30 snapshot :-)

@jengebr
Copy link
Author

jengebr commented Jul 17, 2023

Thank you so much!!! The relevant applications are planning the 5.3 upgrade by October, but I'll do some 5.2.x testing this week and get back to you ASAP.

@jengebr
Copy link
Author

jengebr commented Jul 19, 2023

I confirmed ConstructorResolver works on 5.2. Won't be able to test 5.3 for at least another month but will raise a new, linked issue if something comes up.

Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants