-
Notifications
You must be signed in to change notification settings - Fork 174
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
Significant improvement in deserialization speed #439
base: 2.13
Are you sure you want to change the base?
Conversation
src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCacheNew.kt
Outdated
Show resolved
Hide resolved
// TODO: Is it necessary to call them differently? Direct execution will perform better. | ||
return if (bucket.isFullInitialized() && !instantiator.hasInstanceParameter) { | ||
// we didn't do anything special with default parameters, do a normal call | ||
super.createFromObjectWith(ctxt, jsonParamValueList) | ||
super.createFromObjectWith(ctxt, bucket.values) | ||
} else { | ||
val accessible = callable.isAccessible | ||
if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) || | ||
(accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)) | ||
) { | ||
callable.isAccessible = true | ||
} | ||
val callableParametersByName = linkedMapOf<KParameter, Any?>() | ||
callableParameters.mapIndexed { idx, paramDef -> | ||
if (paramDef != null) { | ||
callableParametersByName[paramDef] = jsonParamValueList[idx] | ||
} | ||
} | ||
callable.callBy(callableParametersByName) | ||
instantiator.checkAccessibility(ctxt) | ||
instantiator.callBy(bucket) | ||
} |
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.
Is it necessary to call the contents of the parent class separately?
When I removed this decision and changed it to the following, the test still ran without any problems.
instantiator.checkAccessibility(ctxt)
return instantiator.callBy(bucket)
In addition, the benchmark score did not change much due to the faster call to the default no-argument constructor.
src/main/kotlin/com/fasterxml/jackson/module/kotlin/ArgumentBucket.kt
Outdated
Show resolved
Hide resolved
src/main/kotlin/com/fasterxml/jackson/module/kotlin/MethodInstantiator.kt
Outdated
Show resolved
Hide resolved
src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCacheNew.kt
Outdated
Show resolved
Hide resolved
src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt
Outdated
Show resolved
Hide resolved
One quick comment: change would definitely need to be against 2.13 branch, to minimize risk of breakage (which I think there is just due to scope). |
I cannot comment a lot on changes, but I like the idea -- one thing that I was always trying to suggest was that as much work as possible SHOULD be done when constructing One thing that would be nice, but I am not sure if it is possible, would be to have choice of two backends for 2.13: "old" one for backwards compatibility, and "new" one with optimizations. Users would have to opt-in (enable) use of the new, faster version, and only use it if it works for them. If and when issues are resolved, in 2.14 and later new backend would become the default. |
@cowtowncoder
That is certainly true. I tried to add a property to switch between the two.
Understood. Also, which is a better way to do this change process, creating a new PR or doing a |
After creating this PR, I noticed that the reflection process on read is also cacheable.
The score is increased to about 4/5 compared to Initially, I intended to make the readout process common between the experimental backend and the conventional backend, but I reverted the commits for commonality in case I want to incorporate this content in the future. Considering the huge size of the PR, I'm thinking of creating a pull request later for caching the reflection process when reading, but should I put it together? |
Looking through this in detail now, as you mention it's big so it'll probably take me a few days. All looks good so far, thank you for the clear explanations. To your last question, yes, a separate PR for the read side makes sense to me. |
@k163377 Would you mind adding some class comments to explain the major intents of the |
|
@dinomite |
@k163377 Great, thanks for clarifying that bitmasking—I'm a bit dense at understanding such things. Things look good to me overall, I'll give another look next week. |
src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorInstantiator.kt
Outdated
Show resolved
Hide resolved
src/main/kotlin/com/fasterxml/jackson/module/kotlin/ConstructorInstantiator.kt
Show resolved
Hide resolved
Ok good improvements! And pretty impressive performance gains @k163377 couple of general notes/answers:
|
One comment/thought inspired by changes here, @dinomite and @k163377 -- now that there are quite a few boolean-valued configuration settings, I wonder if these should grouped as something like Such change should be separate from this PR of course, but I'll introduce idea now. |
Ahh, unfortunate. I was hoping to make those big methods a bit simpler. |
I have a branch that has been modified for 2.13 to make it an official PR. |
@dinomite |
Boolean::class.javaPrimitiveType!! to false, | ||
Char::class.javaPrimitiveType!! to Char.MIN_VALUE, | ||
Byte::class.javaPrimitiveType!! to Byte.MIN_VALUE, | ||
Short::class.javaPrimitiveType!! to Short.MIN_VALUE, |
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.
why use MIN_VALUE instead of zeros?
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullIsSameAsDefault | ||
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullToEmptyCollection | ||
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullToEmptyMap | ||
import com.fasterxml.jackson.module.kotlin.KotlinFeature.* |
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.
suggest to import each single Class instead of *
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 this throughout the project :-/ Without some style enforcement (e.g. ktlint) it's not worth worrying about (though I'd be happy to get a PR adding ktlint…I don't know if it has a Maven plugin).
when this pr can be merged? i'm still waiting... |
I'm sorry I haven't responded for a long time. I don't think it's possible to merge this PR in its current state. Therefore, I am personally thinking of dividing this PR into the following three steps.
However, since the reviewers seem to be very busy, I am going to wait for the current PR to be reviewed and merged first, so as not to burden them further. |
Conflicts: src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinFeature.kt src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt src/test/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModuleTest.kt
@@ -44,6 +44,8 @@ enum class KotlinFeature(private val enabledByDefault: Boolean) { | |||
*/ | |||
StrictNullChecks(enabledByDefault = false); | |||
|
|||
ExperimentalDeserializationBackend(enabledByDefault = false); |
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.
A quick javadoc for this would be good
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullIsSameAsDefault | ||
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullToEmptyCollection | ||
import com.fasterxml.jackson.module.kotlin.KotlinFeature.NullToEmptyMap | ||
import com.fasterxml.jackson.module.kotlin.KotlinFeature.* |
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 this throughout the project :-/ Without some style enforcement (e.g. ktlint) it's not worth worrying about (though I'd be happy to get a PR adding ktlint…I don't know if it has a Maven plugin).
import com.fasterxml.jackson.databind.DeserializationContext | ||
import kotlin.reflect.KParameter | ||
|
||
internal interface Instantiator<T> { |
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.
A javadoc explaining what Instantiator
s are for would be good
private val paramSize: Int, | ||
val values: Array<Any?>, | ||
private val masks: IntArray |
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.
Missing a couple of properties in the javadoc
* | ||
* @property values Arguments arranged in order in the manner of a bucket sort. | ||
*/ | ||
internal class ArgumentBucket( |
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 think this class is complex enough that a unit tests would be helpful, it'd also be great for documenting what it does and how it works
) : StdValueInstantiator(src) { | ||
private fun experimentalCreateFromObjectWith( |
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'd love a test for this…but we don't have any for KVI
right now, so it might be too hard to setup
import kotlin.reflect.KParameter | ||
import kotlin.reflect.full.valueParameters | ||
|
||
internal class MethodInstantiator<T>( |
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 is probably simple enough to test
Ok, I'm back at things. I've looked through the comments on here and resolved most of them, I also brought this up to date with the 2.13 branch. I added some comments of my own, mostly looking for tests on the new code. I'm hoping those aren't too burdensome (I know things like |
Also, I'll be back on this. Once some tests are in place I think this is ready to merge |
@dinomite Therefore, I would like to incorporate all of the contents of this PR after I finish resolving #413. Is it okay if I start from problem 1? |
I'll be merging #512 shortly—what's next for this PR? |
@dinomite
For various reasons, the PR on this will probably be issued sometime this weekend or next Monday. |
This PR still active? |
I have released a project that incorporates these improvements. |
Incorporating several speedup techniques, the deserialization speed has been significantly improved.
The use cases that will be improved are as follows
A simple benchmark showed that in the faster pattern, the entire deserialization was running at twice the current speed.
constructor.Benchmarks.useDefaultKotlinMapper
: Constructor calls with default argumentsfactory.Benchmarks.useDefaultKotlinMapper
: Factory method calls using default argumentsfactory.Benchmarks.useKotlinMapper
:Factory method calls without default argumentsbefore:
after:
In the benchmark after the change, the score with the default arguments is higher, but this is probably due to the difference in the number of arguments to be read.
In principle, if the number of arguments to be read is the same, the benchmark results will be almost the same.
About the speed-up techniques
Direct invocation of
Java reflection
This approach provides the greatest speedup.
Currently, deserialization is done by calling
KFunction
, but this is very slow.Therefore, I have made a speedup by calling
Java reflection
directly.I used
moshi-codegen
as a reference for this idea.The following article may be helpful for the principle of operation.
Caching of
accessibility
/instance
Currently, the reflection
accessibility
is evaluated at each runtime, which hinders the caching ofinstance
.Therefore, the
accessibility
is cached the first time it is looked at, and it is used thereafter.This change in structure makes it possible to cache the
instance
.This will resolve the following comments.
jackson-module-kotlin/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinValueInstantiator.kt
Line 69 in 91d8b35
In addition, it is now easier to determine the fallback.
Avoid
spread operator
This change intentionally avoids using
spread operator
when it can use.Because the
spread operator
inKotlin
has a large execution cost.Specifically, I use means such as copying to an array manually and wrapping variable length argument calls with
Java
.Future benefits
As a side note, I would like to write about the future benefits of this improvement.
With this approach, the deserialization is done only by calling
Java reflection
, which makes it easier to useLambdaMetafactory
for example for further speedup in the future.It will also be easier to remove the
kotlin-reflect
module by incorporating alternatives such askotlinx-metadata
.What I would like to discuss
As you can see from the code, I need to make some very big changes to incorporate this idea.
On the other hand, I don't have enough understanding of the design principles and coding rules of
jackson-module-kotlin
, so I would appreciate your advice.Also, we worked from the
2.12 branch
because of problems running comparison benchmarks, but we are always ready to rebuild it as a PR againstmaster
if needed.I'm very sorry that I'm suddenly throwing out a huge PR.
Thank you.