-
-
Notifications
You must be signed in to change notification settings - Fork 179
Use MethodHandle
in processing related to value class
#1018
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
Conversation
Errors thrown by the constructor of value classes are no longer wrapped in InvocationTargetException.
Errors thrown by the constructor of value classes are no longer wrapped in InvocationTargetException.
with deprecation
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.
Pull Request Overview
This PR replaces reflective invocation with MethodHandle
for value‐class (un)boxing to improve performance and consistency, updates serializers/deserializers to use these handles, and adjusts tests to expect IllegalArgumentException
instead of InvocationTargetException
.
- Migrate caches and converters in
ReflectionCache
to useClass<*>
keys andMethodHandle
‐based converters. - Update serializers/deserializers (
KotlinSerializers
,KotlinKeySerializers
,KotlinDeserializers
, etc.) to use new handle‐based implementations. - Adjust tests in
WithoutCustomDeserializeMethodTest.kt
to expectIllegalArgumentException
and compare it directly.
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
src/test/kotlin/.../WithoutCustomDeserializeMethodTest.kt (mapKey) | Updated assertThrows target and exception assertion to IllegalArgumentException . |
src/test/kotlin/.../WithoutCustomDeserializeMethodTest.kt | Same test update for standalone value class. |
src/main/kotlin/.../ReflectionCache.kt | Changed cache key types and added unbox/box converter caches. |
src/main/kotlin/.../KotlinSerializers.kt | Switched ValueClassSerializer to handle classes via MethodHandle . |
src/main/kotlin/.../KotlinModule.kt | Pass ReflectionCache into serializers/key‐serializers. |
src/main/kotlin/.../KotlinKeySerializers.kt | Refactored key serializers to use handle‐based implementation. |
src/main/kotlin/.../KotlinKeyDeserializers.kt | Introduced handle‐based key deserializers with specialized wrappers. |
src/main/kotlin/.../KotlinDeserializers.kt | Updated value‐class deserializers to use MethodHandle , split by conversion type. |
src/main/kotlin/.../InternalCommons.kt | Added shared MethodType constants and handle utilities. |
src/main/kotlin/.../Converters.kt | Rewrote converters to ValueClassBoxConverter /UnboxConverter with MethodHandle . |
pom.xml | Updated javadoc exclusions for new/renamed internal classes. |
Comments suppressed due to low confidence (1)
src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt:41
- The LRUMap is initialized with
0
as the initial capacity andreflectionCacheSize
as the max, but originally both parameters were set toreflectionCacheSize
. Using0
may lead to immediate eviction or unexpected behavior. ConsiderLRUMap(reflectionCacheSize, reflectionCacheSize)
to match its intended capacity.
LRUMap(0, reflectionCacheSize)
...odule/kotlin/kogeraIntegration/deser/valueClass/mapKey/WithoutCustomDeserializeMethodTest.kt
Show resolved
Hide resolved
MethodHandle
in processing related to value class
MethodHandle
in processing related to value class
Background
To seamless handling of value classes with Jackson, reflection is heavily used in the related processing.
In many cases, multiple reflective calls are performed in sequence.
For example, during deserialization, at least two reflective calls are required: invoking the primary constructor and then a call to
box
.Similarly, serialization typically requires at least two reflective calls — one to
box
and one tounbox
.Replacing these reflective operations with MethodHandles is expected to performance improvement.
Modification details
Each of the parts that used to use
Method
are replaced withMethodHandle
.In particular, the parts of the process in
kotlin-module
whereMethod
was called continuously are further optimized by composingMethodHandle
usingMethodHandle.filterReturnValue
.In addition, for
Int
,Long
,String
, and (Java
)UUID
, which are particularly common types wrapped invalue class
, separate optimizations are made to speed up invocation by making the types explicit.This change is based on the implementation in
jackson-module-kogera 2.19.0-beta25
.https://github.com/ProjectMapK/jackson-module-kogera/releases/tag/2.19.0-beta25
It also fixes a problem where the
unbox-impl
method was not properly cached.In particular, the problem of getting
unbox-impl
on every run in several cases related to serialization has been fixed, which will further greatly increase throughput in those cases.Breaking change
Due to a change in execution, exceptions thrown by the constructor of
value class
are no longer wrapped in anInvocationTargetException
.Benchmark
The following repository was used for benchmarking:
k163377/jackson-value-class-benchmark
The commits to be compared are f54ef6e(before improvement) and d81fb82(after improvement).
Benchmark Details
The benchmark should compare performance with and without the aforementioned type-specific optimization.
Additionally, the benchmark was designed to minimize the overall processing done by
Jackson
itself, in order to better isolate and observe the impact of theMethodHandle
based performance enhancements.With these considerations in mind, the benchmarks were structured as follows:
value class
types were tested: one wrapping anInt
(with type-specific optimization), and one wrapping aShort
(without type-specific optimization)Since the benefits of this optimization increase with the number of
value class
properties involved, this benchmark serves as a lower bound for the performance improvements achievable invalue class
handling.However, a preliminary check by
kogera
did not explicitly confirm the effect of type-specific optimizations in this benchmark because the percentage of processing related tovalue class
was too low.This is summarized in the following article(sorry, only in Japanese).
https://wrongwrong163377.hatenablog.com/entry/2025/07/05/175237
Also,
kotlin-module
has lower deserialization performance thankogera
, so the amount of improvement seen from the benchmark is lower than that ofkogera
.Throughput
A ratio greater than 1 indicates improvement.
summarized at: https://docs.google.com/spreadsheets/d/1Qi60pL8SEXJA5dx-CJA_NrON-OahTyb9RM5jDUhVk94
The overall trend is read as an improvement.
Serialize
In all cases, the scores showed a speedup in all cases.
In particular, the most common case,
unbox.IntBenchmark.wrapped
, showed more than a 2X speedup.Deserialize
In all cases, the scores showed a speedup in all cases.
In particular, the most common
byPrimaryConstructor.IntBenchmark.wrapped
case showed an improvement of about 3%.Single Shot
A ratio less than 1 indicates improvement.
summarized at: https://docs.google.com/spreadsheets/d/1zAr0zH6AVeOGdWz6WJd5-42jz3FrK1aHR-AmIiVNaaY
Since the amount of initialization processing is increasing, the overall trend can be read as degradation.
However, the amount of degradation itself is at worst about 6%, and in many cases it is less than 3%.
Serialize
Deserialize