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

Startup time improvement #4

Open
wants to merge 5 commits into
base: 5.6
Choose a base branch
from

Conversation

Postremus
Copy link

@Postremus Postremus commented Feb 26, 2022

Relates to https://quarkusio.zulipchat.com/#narrow/stream/187030-users/topic/startup.20.2F.20reloading.20performance

d87ddc0
Avoids a duplicate getParameterTypes() call, just for good measure.

37b219c
Saves about 700ms.

8145598
Saves another 1s. This replaces a getDeclaredField call by getDeclaredFields and an iteration. Exceptions are apperantly more costly than creating lots of arrays copies.

8c319d7
Saves another 300ms. This is the commit you worried about, implementing a simple cache for the class instances declared methods and fields.

@Postremus Postremus force-pushed the startup-time-improvement branch from 8c319d7 to 87a6faa Compare February 26, 2022 07:26
@Postremus
Copy link
Author

I will refrain from further tinkering on this problem (though it is fun).
Further ideas and experimentation (e.g. a faster java.beans.Introspector#decapitalize implementation) will be done seperatly.
Lets focus next on getting some of these changes integrated.

I listed the essential commits in the issue description. The last two (8d929bb and 2d9d3a5) combined save about another 20ms.

@@ -525,8 +525,7 @@ private static Method getGetterOrNull(Class containerClass, String propertyName)
// try "get"
if ( methodName.startsWith( "get" ) ) {
final String stemName = methodName.substring( 3 );
final String decapitalizedStemName = Introspector.decapitalize( stemName );
if ( stemName.equals( propertyName ) || decapitalizedStemName.equals( propertyName ) ) {
if ( stemName.equals( propertyName ) || Introspector.decapitalize( stemName ).equals( propertyName ) ) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch!

@@ -49,6 +51,9 @@
private static final Method OBJECT_EQUALS;
private static final Method OBJECT_HASHCODE;

private static final Map<Class<?>, Method[]> METHODS_BY_CLASS = new HashMap<>();
private static final Map<Class<?>, Field[]> FIELDS_BY_CLASS = new HashMap<>();
Copy link
Owner

@Sanne Sanne Mar 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is not possible for us to do, unfortunately. We can't have the Hibernate classloader hold a "hard reference" to a class instance of the application: many application servers woul start leaking memory, as the Hibernate module is long-lived while applications (and user classes) are often undeployed, redeployed, etc.. sometimes many times.

What we can do is to introuce a new class type IntrospectedClass (or anything) to store together: the user class, its methods, its fields. The methods array and the fields array could be lazily initialized.
Then you refactor this class to consistently use that new IntropectedClass instead of the current Class.

If I'm unclear let me know, I'm happy to show you what I mean :)

This approach would also benefit from not using excessive map inserts/lookups.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Sanne,

sorry for the late reply, had to focus my creativity on real (as in paid) work for the last few weeks.

I gave your suggestion a shot in Postremus@7d02e58.

I quickly noticed though that, as far as I understand, this won't work. IntrospectedClass is a clean idea to solve the problem of hard references on class instances.
However, the current structure of ORM does not lend itself to this concept.

Using IntrospectedClass would also mean using it in all call sites and their call sites in turn. This would result in way to many changes, and also needs changes in spi interfaces (e.g. PropertyAccess, Getter, Setter).
Otherwise IntrospectedClass is always for each field access, rendering it useless.

I have switched the commit order around abit (I love interactive rebasing) - this commit is now the last. I am okay with simply dropping it.
ORM 6 is dropping in the next few weeks anyway, where work could be started on using jandex instead of reflection.

@Postremus Postremus force-pushed the startup-time-improvement branch from 2d9d3a5 to 380634b Compare March 1, 2022 20:28
@Postremus Postremus force-pushed the startup-time-improvement branch from 380634b to 3c05d93 Compare March 14, 2022 05:35
@Postremus
Copy link
Author

FTR, there is one related test failure, which I will have to look into.

2022-03-14T05:39:50.7790151Z org.hibernate.property.GetAndIsVariantGetterTest > testHbmXml FAILED
2022-03-14T05:39:50.7791307Z     java.lang.AssertionError at GetAndIsVariantGetterTest.java:60

I also dropped the caching commit for the reasons mentioned above.

This saves on throwing exceptions everytime a field was not found, for the cost of copying the fields arrays on each invocation. This is still magnitudes faster though.
@Postremus Postremus force-pushed the startup-time-improvement branch 2 times, most recently from da70482 to e7f01ff Compare March 14, 2022 21:02
@Postremus
Copy link
Author

All tests are passing now.

@Postremus
Copy link
Author

@Sanne friendly ping. :)

@Postremus Postremus requested a review from Sanne April 19, 2022 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants