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
HV-1694 HV-1695 HV-1696 HV-1697 Reduce memory allocations #1020
Conversation
It generates too much noise, in particular when testing unconstrained beans.
This way we can compare current to latest released 6.1
efbddab
to
3c49312
Compare
@marko-bekhta I added one further improvements to ValidationContexts. This one makes a nice difference in the I also added a
for the |
return beanMetaData; | ||
} | ||
|
||
beanMetaData = (BeanMetaData<T>) beanMetaDataCache.computeIfAbsent( beanClass, | ||
bc -> createBeanMetaData( bc ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is interesting: this code creates a new instance of the lambda for each call so we end up creating a lot of lambda instances.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm... so it looks like the ConcurrentReferenceHashMap
that we are using for this cache is actually using the default impl of computeIfAbsent
. And that default one doesn't have any synchronised blocks. So I wonder if maybe we should change this last line to something like:
BeanMetaDataImpl<T> beanMetaData = createBeanMetaData( beanClass );
beanMetaDataCache.putIfAbsent( beanClass, beanMetaData );
return beanMetaData;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. So I switched to that but I get the previous value from putIfAbsent
and return that if there is one.
@gunnarmorling if you have some time, could you take a look at this one? Better reviewed commit per commit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice improvements! I've added a few comments/questions inline. Now - benchmarks!
@@ -245,7 +245,7 @@ | |||
<properties> | |||
<validation-api.version>1.1.0.Final</validation-api.version> | |||
<beanvalidation-impl.name>Hibernate Validator</beanvalidation-impl.name> | |||
<beanvalidation-impl.version>5.4.2.Final</beanvalidation-impl.version> | |||
<beanvalidation-impl.version>5.4.3.Final</beanvalidation-impl.version> | |||
</properties> | |||
<dependencies> | |||
<dependency> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cannot leave a comment on a renamed file below. Not sure why SimpleComposingConstraintValidation
got moved to bv2 sources ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it's using BV 2 constraints :).
I had an issue when trying to build with BV 1.1 and couldn't find nice BV 1.1 constraints so I just decided to move it as it's more a test to validate recent performance improvements.
return Collections.emptySet(); | ||
} | ||
|
||
BaseBeanValidationContext<T> validationContext = getValidationContextBuilder().forValidate( rootBeanClass, rootBeanMetaData, object ); | ||
|
||
if ( !validationContext.getRootBeanMetaData().hasConstraints() ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this one is probably redundant now as we already checked for constraints on line 158.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, sure it was supposed to be removed, missed it, will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, there were a few other glitches. I fixed them all, hopefully.
return beanMetaData; | ||
} | ||
|
||
beanMetaData = (BeanMetaData<T>) beanMetaDataCache.computeIfAbsent( beanClass, | ||
bc -> createBeanMetaData( bc ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm... so it looks like the ConcurrentReferenceHashMap
that we are using for this cache is actually using the default impl of computeIfAbsent
. And that default one doesn't have any synchronised blocks. So I wonder if maybe we should change this last line to something like:
BeanMetaDataImpl<T> beanMetaData = createBeanMetaData( beanClass );
beanMetaDataCache.putIfAbsent( beanClass, beanMetaData );
return beanMetaData;
…Impl It significantly reduces memory allocation as it avoids the creation of a lambda instance.
Here's the results.. Some of them are a bit unexpected (6.0 unconstrained): hv-current:
# Run complete. Total time: 00:00:36
Benchmark Mode Cnt Score Error Units
SimpleValidation.testSimpleBeanValidation thrpt 20 3580.428 ± 250.562 ops/ms
UnconstrainedBeanValidation.testUnconstrainedBeanValidation thrpt 20 7065.427 ± 107.262 ops/ms
6.1.alpha
# Run complete. Total time: 00:00:37
Benchmark Mode Cnt Score Error Units
SimpleValidation.testSimpleBeanValidation thrpt 20 3176.884 ± 219.034 ops/ms
UnconstrainedBeanValidation.testUnconstrainedBeanValidation thrpt 20 7429.412 ± 114.166 ops/ms
6.0
# Run complete. Total time: 00:00:37
Benchmark Mode Cnt Score Error Units
SimpleValidation.testSimpleBeanValidation thrpt 20 2799.963 ± 76.101 ops/ms
UnconstrainedBeanValidation.testUnconstrainedBeanValidation thrpt 20 9013.694 ± 95.510 ops/ms
UnconstrainedBeanValidation.testUnconstrainedBeanValidation thrpt 20 7882.279 ± 104.074 ops/ms
5.4
# Run complete. Total time: 00:00:30
Benchmark Mode Cnt Score Error Units
SimpleValidation.testSimpleBeanValidation thrpt 20 334.601 ± 6.362 ops/ms
UnconstrainedBeanValidation.testUnconstrainedBeanValidation thrpt 20 7126.291 ± 102.859 ops/ms
5.2
# Run complete. Total time: 00:00:31
Benchmark Mode Cnt Score Error Units
SimpleValidation.testSimpleBeanValidation thrpt 20 337.434 ± 10.004 ops/ms
UnconstrainedBeanValidation.testUnconstrainedBeanValidation thrpt 20 7541.114 ± 123.081 ops/ms |
Yeah I had the same sort of issues for the unconstrained benchmark. I don't know what's going on exactly. |
I just run it again and I have: hv-current
hv-6.0
Which JDK do you use to run the benchmarks? I use a good old JDK 8. |
And with JDK 11.0.1, it's significantly slower, especially |
Just tried it again with your latest change:
6.0:
And I'm on VM version: JDK 1.8.0_161, VM 25.161-b12 |
This is weird to say the least because it definitely does less work. Could you check what's going on with a profiler? Not sure it will give interesting results though :/. Wondering if maybe the method was inlined and that it's now too big to be inlined. |
@marko-bekhta I pushed an additional commit pursuing the idea of inlining. It seems to improve things here (but tbh, it's not very stable). |
Yeah I tried these last changes as well. And I cannot explain the behavior. I run benchmarks on different env today. When I tried them with |
OK, so you have weird results even with this very last commit: 581be9a ? |
Yes |
@marko-bekhta so I discussed with @barreiro from our performance team and it appears our usage of I changed our code to use With 6.1:
With current:
So the new code appears to be 20 times faster on my laptop. Could you check you have a similar tendency? |
Hi @gsmet yes, I just saw that you've pushed new changes and run the benchmarks right away 😄 . My results are similar: hv-curr
# Run complete. Total time: 00:00:41
Benchmark Mode Cnt Score Error Units
UnconstrainedBeanValidation.testUnconstrainedBeanValidation thrpt 20 88921.833 ± 1115.665 ops/ms
Benchmark Mode Cnt Score Error Units
SimpleValidation.testSimpleBeanValidation thrpt 20 1867.365 ± 39.177 ops/ms
hv-6.0
# Run complete. Total time: 00:00:43
Benchmark Mode Cnt Score Error Units
UnconstrainedBeanValidation.testUnconstrainedBeanValidation thrpt 20 13163.132 ± 258.242 ops/ms
Benchmark Mode Cnt Score Error Units
SimpleValidation.testSimpleBeanValidation thrpt 20 1032.561 ± 17.536 ops/ms Nice that we have an explanation of that behavior. |
@marko-bekhta could you take a look at this one?
We had some reports of higher memory allocation rate when validating unconstrained beans (compared to old 5.2) and I can see how that could be, considering how we restructured the code: we would create a context even if not required at all.
That being said, benchmarks don't really say it's faster but I can see at least less allocations which is good.
I also modified some of the benchmarks so that they allocate less things in the benchmark itself. It avoids polluting the results.
Better reviewed commit per commit.
Note that there are a lot more things we could do to improve memory allocation rate when bootstrapping the metadata but it's less pressing so maybe one day :) - one thing I noticed is that we should cache
getAnnotatedType()
andgetAnnotations()
in theorg.hibernate.validator.internal.properties.javabean
classes. Not totally sure it's worth the bandwidth though so enough for one day.