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
Cache types in ConstructorMapper #2650
Conversation
@@ -75,6 +75,9 @@ public final class ConstructorMapper<T> implements RowMapper<T> { | |||
+ "that your result set has the columns expected, annotate the " | |||
+ "parameter names explicitly with @ColumnName, or annotate nullable parameters as @Nullable"; | |||
|
|||
|
|||
static final Map<Constructor, Type[]> CONSTRUCTOR_CACHE = new ConcurrentHashMap<>(); |
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.
would it be simpler to have that in the factory and make it private?
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.
I'll try that out, thanks!
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.
updated. Originally I was going to use a guava cache but I see that guava is only imported in test scope. lmk if you think it's a good idea to add a size limit, cache stats, ability to clear cache, disable cache (maybe for benchmarking), etc.
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.
We do not depend on Guava from Jdbi core, since it is a huge dep to push onto our users. Additionally, there are numerous known cache bugs that seemingly will never get fixed. The Guava CacheBuilder
javadocs tell you to use Caffeine instead to avoid such bugs.
I think for a simple use case like this, a synchronized weak hash map is good enough. A map that caches JVM entities like constructors and classes will not grow without bound, so there is no need to size it.
bb0bd84
to
2da68e8
Compare
Looks good. Lmk when you move it from draft to ready. |
Updated, lmk if it's good to go whenever you have a chance, thanks! |
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.
One note about inadvertent retention of classes. Otherwise LGTM
@@ -27,19 +29,18 @@ | |||
import static java.util.Objects.requireNonNull; | |||
|
|||
class ConstructorInstanceFactory<T> extends InstanceFactory<T> { | |||
private final Constructor<T> constructor; | |||
private static final Map<Constructor, Type[]> CONSTRUCTOR_CACHE = new ConcurrentHashMap<>(); |
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 will prevent the JVM from unloading any class that the ConstructorMapper
ever considers, since it keeps a static strong reference to the Constructor
.
Would the change work just as well with a synchronizedMap(new WeakHashMap<>())
or something like that? Then, the JVM would be able to unload classes since we do not strongly retain their Constructors.
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.
sounds good, will try all of these ideas out and update, thanks!
This is a broader change than the scope that you're trying to accomplish here, but I think the performance would be even better if we construct and cache a |
One last thought: it would be nice if we could see the improvement in a benchmark in |
2da68e8
to
4024520
Compare
Updated, will run the benchmarks on the commit before the record constructor workaround, before the this commit and then after this commit and post the results to the pr. Thanks for all the ideas! |
4024520
to
afafbbc
Compare
Just working through the ci failures, will update. |
Update: added a commit to cache the types and method handle in the instance factory. I'm running the |
|
||
class ConstructorInstanceFactory<T> extends InstanceFactory<T> { | ||
private final Constructor<T> constructor; | ||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); |
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.
lmk if this shouldn't be cached.
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.
Let me know if you want the weak hashmap added back vs caching in the constructor instance factory (saved the previous version, so I can quickly update).
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.
So I think there is a bit of a misunderstanding here. I didn't mean to cache the lookup factory instance itself, but the constructorHandle
instance itself.
Something like SynchronizedMap<WeakHashMap<ConstructorHandleAndTypes, MethodHandle>>>
(referring to the key type from the other PR)
Sorry I wasn't clear, and sorry for taking so long to respond! Been a busy couple weeks at work.
|
||
class ConstructorInstanceFactory<T> extends InstanceFactory<T> { | ||
private final Constructor<T> constructor; | ||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); |
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.
Let me know if you want the weak hashmap added back vs caching in the constructor instance factory (saved the previous version, so I can quickly update).
@@ -207,14 +207,14 @@ private Optional<RowMapper<T>> createSpecializedRowMapper(StatementContext ctx, | |||
List<String> unmatchedColumns) { | |||
final int count = factory.getParameterCount(); | |||
final Parameter[] parameters = factory.getParameters(); | |||
final Type[] types = factory.getTypes(); | |||
final List<Type> types = factory.getTypes(); |
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.
Had to convert to list due to error about exposing an array. Thinking to change the api to store the array internally and change the signature to getType(index)
, wdyt?
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.
That sounds ok
@@ -44,10 +46,10 @@ Parameter[] getParameters() { | |||
return executable.getParameters(); | |||
} | |||
|
|||
Type[] getTypes() { | |||
List<Type> getTypes() { |
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.
same here - should it be Type getType(int index)
?
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.
OK by me
Quality Gate passedIssues Measures |
Here are the benchmarks: Benchmark with the code in this pr:
Benchmark for the weak hashmap version of this pr:
Benchmark before the record constructor pr:
|
lmk if you prefer this version or the weak hashmap version (static cache w synchronized weak hashmap). This version caches the method handle and types directly in the instance factory. |
Merging #2657 . Thanks! |
Cache types to avoid expensive calculations in ConstructorInstanceFactory.
Following up on suggestions from #2648.
The constructor lookup should be quick:
hashCode
is class name andequals
compares parameters using object equality.