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

Eliminate redundant lookups of classes and annotations #30891

Closed
wants to merge 1 commit into from

Conversation

jengebr
Copy link

@jengebr jengebr commented Jul 14, 2023

Repeatedly instantiating beans can trigger redundant lookups of classes and parameter annotations, and in certain applications become noticeable performance issues. This change eliminates the redundant work by a) changing MethodInvoker.prepare() to only lookup targetClass when it's missing, and b) changing InjectionPoint to cache the method annotations similarly to the existing caching for field annotations.

Closes: #30883

@pivotal-cla
Copy link

@jengebr Please sign the Contributor License Agreement!

Click here to manually synchronize the status of this Pull Request.

See the FAQ for frequently asked questions.

@pivotal-cla
Copy link

@jengebr Thank you for signing the Contributor License Agreement!

@jhoeller
Copy link
Contributor

Which usage of MethodInvoker are you seeing as a hotspot, actually? We usually only call prepare() once for our internally prepared MethodInvoker instances, so I wonder how that optimization would make a difference. If you'd like to optimize the initial preparation of a static method there, I'd rather recommend setting targetClass+targetMethod instead of staticMethod. Setting targetClass+staticMethod is not an intended combination, and in case of a reinitialization attempt, we mean to let staticMethod override any configuration set in targetClass/targetMethod before.

As for the InjectionPoint part, MethodParameter.getParameterAnnotations() itself caches the annotation array. Since the MethodParameter instance itself is always the same there, I don't see a benefit in locally caching the annotation array in InjectionPoint as well. If something is going wrong with the existing cache intentions that we have in MethodParameter, we should rather aim to fix it there.

@jhoeller
Copy link
Contributor

Oh, and thanks for your optimization attempts there, that's much appreciated! I'd be happy to fine-tune the caching that we have there already etc. Any changes that make a measurable difference for you are worth investigating, finding out why existing caching does not cover it already. It's just that the right place to address such shortcomings is not always obvious (even to us).

@jengebr
Copy link
Author

jengebr commented Jul 14, 2023

Thanks for reviewing this! Our apps run 5.2.x so it's always possible this is comes from a version difference.

And you summarized the situation quite well with this:

It's just that the right place to address such shortcomings is not always obvious (even to us).

Re: InjectionPoint, I see what you're saying, and I'll look upwards through the call tree to see what else may be at fault.

Re: MethodInvoker, I'm not able to identify the specific bean configuration(s) causing this in production, but reinitialization is an interesting case that I hadn't considered, and it might explain all of this. It shouldn't be happening, but it's conceivable.

Detailed stacks are below.


The hot stack for MethodInvoker is:

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
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

and for InjectionPoint is:

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
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

@jhoeller
Copy link
Contributor

jhoeller commented Jul 14, 2023

The problem for InjectionPoint seems to be that we're not caching the DependencyDescriptor instances for prepared constructor arguments but rather just an autowiring marker there. For @Autowired fields and methods, we cache the DependencyDescriptor instances in AutowiredAnnotationBeanPostProcessor, so we should also do so in ConstructorResolver (with a specific marker subclass of DependencyDescriptor). I'll push a corresponding revision of ConstructorResolverto main, 6.0.x and 5.3.x ASAP (with a reference to #30883).

With the MethodInvoker, I suspect that your MethodInvokingFactoryBean definitions are non-singleton, in which case it would re-initialize a fresh MethodInvokingFactoryBean instance every time. You could define the MethodInvokingFactoryBean itself as a singleton and switch its singleton property to false instead, sharing the FactoryBean instance but letting it perform a fresh target method call for every getObject invocation. Also, it should make a difference here when switching from staticMethod to targetClass+targetMethod configuration since the container will cache the resolved Class argument for targetClass, even on a non-singleton definition.

@jhoeller jhoeller added status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jul 15, 2023
@jhoeller
Copy link
Contributor

This should be fully superseded by my resolution for #30883 now, covering all identified hotspots.

@jhoeller jhoeller closed this Jul 15, 2023
@sbrannen sbrannen added in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement labels Jul 15, 2023
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: duplicate A duplicate of another issue type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Performance bottlenecks while creating scoped bean instances
5 participants