Skip to content

Commit d7f228e

Browse files
authored
Merge pull request #1018 from k163377/upgrade-value-class-perf
Use `MethodHandle` in processing related to `value class`
2 parents 39469a1 + d6f64c8 commit d7f228e

File tree

13 files changed

+630
-99
lines changed

13 files changed

+630
-99
lines changed

pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,22 @@
255255
<exclude>
256256
com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException#MissingKotlinParameterException(kotlin.reflect.KParameter,java.io.Closeable,java.lang.String)
257257
</exclude>
258+
<exclude>
259+
com.fasterxml.jackson.module.kotlin.WrapsNullableValueClassBoxDeserializer
260+
</exclude>
261+
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassUnboxKeySerializer</exclude>
262+
<exclude>com.fasterxml.jackson.module.kotlin.KotlinKeySerializersKt</exclude>
263+
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassSerializer</exclude>
264+
<!-- internal -->
265+
<exclude>
266+
com.fasterxml.jackson.module.kotlin.KotlinKeySerializers#KotlinKeySerializers()
267+
</exclude>
268+
<exclude>
269+
com.fasterxml.jackson.module.kotlin.KotlinSerializers#KotlinSerializers()
270+
</exclude>
271+
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassStaticJsonKeySerializer</exclude>
272+
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassBoxConverter</exclude>
273+
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassKeyDeserializer</exclude>
258274
</excludes>
259275
</parameter>
260276
</configuration>

release-notes/CREDITS-2.x

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Contributors:
1818
# 2.20.0 (not yet released)
1919

2020
WrongWrong (@k163377)
21+
* #1018: Use MethodHandle in processing related to value class
2122
* #969: Cleanup of deprecated contents
2223
* #967: Update settings for 2.20
2324

release-notes/VERSION-2.x

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ Co-maintainers:
1717
------------------------------------------------------------------------
1818

1919
2.20.0 (not yet released)
20-
20+
#1018: Improved handling of `value class` has improved performance for both serialization and deserialization.
21+
In particular, for serialization, proper caching has improved throughput by a factor of 2 or more in the general cases.
22+
Also, replacing function execution by reflection with `MethodHandle` improved throughput by several percent for both serialization and deserialization.
23+
In cases where the number of properties of a `value class` in the processing target is large, there is a possibility to obtain a larger improvement.
24+
Please note that this modification causes a destructive change in that exceptions thrown during deserialization of
25+
`value class` are no longer wrapped in an `InvocationTargetException`.
2126
#969: Deprecated content has been cleaned up with the version upgrade.
2227
#967: Kotlin has been upgraded to 2.0.21.
2328
- Generate SBOMs [JSTEP-14]

src/main/kotlin/com/fasterxml/jackson/module/kotlin/Converters.kt

Lines changed: 161 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import com.fasterxml.jackson.databind.JavaType
44
import com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer
55
import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer
66
import com.fasterxml.jackson.databind.type.TypeFactory
7-
import com.fasterxml.jackson.databind.util.ClassUtil
87
import com.fasterxml.jackson.databind.util.StdConverter
9-
import kotlin.reflect.KClass
8+
import java.lang.invoke.MethodHandle
9+
import java.lang.invoke.MethodHandles
10+
import java.lang.invoke.MethodType
11+
import java.lang.reflect.Method
12+
import java.lang.reflect.Type
13+
import java.util.UUID
1014
import kotlin.time.toJavaDuration
1115
import kotlin.time.toKotlinDuration
1216
import java.time.Duration as JavaDuration
@@ -23,7 +27,7 @@ internal class SequenceToIteratorConverter(private val input: JavaType) : StdCon
2327
}
2428

2529
internal object KotlinDurationValueToJavaDurationConverter : StdConverter<Long, JavaDuration>() {
26-
private val boxConverter by lazy { ValueClassBoxConverter(Long::class.java, KotlinDuration::class) }
30+
private val boxConverter by lazy { LongValueClassBoxConverter(KotlinDuration::class.java) }
2731

2832
override fun convert(value: Long): JavaDuration = KotlinToJavaDurationConverter.convert(boxConverter.convert(value))
2933
}
@@ -45,18 +49,163 @@ internal object JavaToKotlinDurationConverter : StdConverter<JavaDuration, Kotli
4549
}
4650
}
4751

48-
// S is nullable because value corresponds to a nullable value class
49-
// @see KotlinNamesAnnotationIntrospector.findNullSerializer
50-
internal class ValueClassBoxConverter<S : Any?, D : Any>(
51-
unboxedClass: Class<S>,
52-
val boxedClass: KClass<D>
53-
) : StdConverter<S, D>() {
54-
private val boxMethod = boxedClass.java.getDeclaredMethod("box-impl", unboxedClass).apply {
55-
ClassUtil.checkAndFixAccess(this, false)
52+
internal sealed class ValueClassBoxConverter<S : Any?, D : Any> : StdConverter<S, D>() {
53+
abstract val boxedClass: Class<D>
54+
abstract val boxHandle: MethodHandle
55+
56+
protected fun rawBoxHandle(
57+
unboxedClass: Class<*>,
58+
): MethodHandle = MethodHandles.lookup().findStatic(
59+
boxedClass,
60+
"box-impl",
61+
MethodType.methodType(boxedClass, unboxedClass),
62+
)
63+
64+
val delegatingSerializer: StdDelegatingSerializer by lazy { StdDelegatingSerializer(this) }
65+
66+
companion object {
67+
fun create(
68+
unboxedClass: Class<*>,
69+
valueClass: Class<*>,
70+
): ValueClassBoxConverter<*, *> = when (unboxedClass) {
71+
Int::class.java -> IntValueClassBoxConverter(valueClass)
72+
Long::class.java -> LongValueClassBoxConverter(valueClass)
73+
String::class.java -> StringValueClassBoxConverter(valueClass)
74+
UUID::class.java -> JavaUuidValueClassBoxConverter(valueClass)
75+
else -> GenericValueClassBoxConverter(unboxedClass, valueClass)
76+
}
5677
}
5778

79+
// If the wrapped type is explicitly specified, it is inherited for the sake of distinction
80+
internal sealed class Specified<S : Any?, D : Any> : ValueClassBoxConverter<S, D>()
81+
}
82+
83+
// region: Converters for common classes as wrapped values, add as needed.
84+
internal class IntValueClassBoxConverter<D : Any>(
85+
override val boxedClass: Class<D>,
86+
) : ValueClassBoxConverter.Specified<Int, D>() {
87+
override val boxHandle: MethodHandle = rawBoxHandle(Int::class.java).asType(INT_TO_ANY_METHOD_TYPE)
88+
89+
@Suppress("UNCHECKED_CAST")
90+
override fun convert(value: Int): D = boxHandle.invokeExact(value) as D
91+
}
92+
93+
internal class LongValueClassBoxConverter<D : Any>(
94+
override val boxedClass: Class<D>,
95+
) : ValueClassBoxConverter.Specified<Long, D>() {
96+
override val boxHandle: MethodHandle = rawBoxHandle(Long::class.java).asType(LONG_TO_ANY_METHOD_TYPE)
97+
98+
@Suppress("UNCHECKED_CAST")
99+
override fun convert(value: Long): D = boxHandle.invokeExact(value) as D
100+
}
101+
102+
internal class StringValueClassBoxConverter<D : Any>(
103+
override val boxedClass: Class<D>,
104+
) : ValueClassBoxConverter.Specified<String?, D>() {
105+
override val boxHandle: MethodHandle = rawBoxHandle(String::class.java).asType(STRING_TO_ANY_METHOD_TYPE)
106+
107+
@Suppress("UNCHECKED_CAST")
108+
override fun convert(value: String?): D = boxHandle.invokeExact(value) as D
109+
}
110+
111+
internal class JavaUuidValueClassBoxConverter<D : Any>(
112+
override val boxedClass: Class<D>,
113+
) : ValueClassBoxConverter.Specified<UUID?, D>() {
114+
override val boxHandle: MethodHandle = rawBoxHandle(UUID::class.java).asType(JAVA_UUID_TO_ANY_METHOD_TYPE)
115+
116+
@Suppress("UNCHECKED_CAST")
117+
override fun convert(value: UUID?): D = boxHandle.invokeExact(value) as D
118+
}
119+
// endregion
120+
121+
/**
122+
* A converter that only performs box processing for the value class.
123+
* Note that constructor-impl is not called.
124+
* @param S is nullable because value corresponds to a nullable value class.
125+
* see [io.github.projectmapk.jackson.module.kogera.annotationIntrospector.KotlinFallbackAnnotationIntrospector.findNullSerializer]
126+
*/
127+
internal class GenericValueClassBoxConverter<S : Any?, D : Any>(
128+
unboxedClass: Class<S>,
129+
override val boxedClass: Class<D>,
130+
) : ValueClassBoxConverter<S, D>() {
131+
override val boxHandle: MethodHandle = rawBoxHandle(unboxedClass).asType(ANY_TO_ANY_METHOD_TYPE)
132+
58133
@Suppress("UNCHECKED_CAST")
59-
override fun convert(value: S): D = boxMethod.invoke(null, value) as D
134+
override fun convert(value: S): D = boxHandle.invokeExact(value) as D
135+
}
136+
137+
internal sealed class ValueClassUnboxConverter<S : Any, D : Any?> : StdConverter<S, D>() {
138+
abstract val valueClass: Class<S>
139+
abstract val unboxedType: Type
140+
abstract val unboxHandle: MethodHandle
141+
142+
final override fun getInputType(typeFactory: TypeFactory): JavaType = typeFactory.constructType(valueClass)
143+
final override fun getOutputType(typeFactory: TypeFactory): JavaType = typeFactory.constructType(unboxedType)
60144

61145
val delegatingSerializer: StdDelegatingSerializer by lazy { StdDelegatingSerializer(this) }
146+
147+
companion object {
148+
fun create(valueClass: Class<*>): ValueClassUnboxConverter<*, *> {
149+
val unboxMethod = valueClass.getDeclaredMethod("unbox-impl")
150+
val unboxedType = unboxMethod.genericReturnType
151+
152+
return when (unboxedType) {
153+
Int::class.java -> IntValueClassUnboxConverter(valueClass, unboxMethod)
154+
Long::class.java -> LongValueClassUnboxConverter(valueClass, unboxMethod)
155+
String::class.java -> StringValueClassUnboxConverter(valueClass, unboxMethod)
156+
UUID::class.java -> JavaUuidValueClassUnboxConverter(valueClass, unboxMethod)
157+
else -> GenericValueClassUnboxConverter(valueClass, unboxedType, unboxMethod)
158+
}
159+
}
160+
}
161+
}
162+
163+
internal class IntValueClassUnboxConverter<T : Any>(
164+
override val valueClass: Class<T>,
165+
unboxMethod: Method,
166+
) : ValueClassUnboxConverter<T, Int>() {
167+
override val unboxedType: Type get() = Int::class.java
168+
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_INT_METHOD_TYPE)
169+
170+
override fun convert(value: T): Int = unboxHandle.invokeExact(value) as Int
171+
}
172+
173+
internal class LongValueClassUnboxConverter<T : Any>(
174+
override val valueClass: Class<T>,
175+
unboxMethod: Method,
176+
) : ValueClassUnboxConverter<T, Long>() {
177+
override val unboxedType: Type get() = Long::class.java
178+
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_LONG_METHOD_TYPE)
179+
180+
override fun convert(value: T): Long = unboxHandle.invokeExact(value) as Long
181+
}
182+
183+
internal class StringValueClassUnboxConverter<T : Any>(
184+
override val valueClass: Class<T>,
185+
unboxMethod: Method,
186+
) : ValueClassUnboxConverter<T, String?>() {
187+
override val unboxedType: Type get() = String::class.java
188+
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_STRING_METHOD_TYPE)
189+
190+
override fun convert(value: T): String? = unboxHandle.invokeExact(value) as String?
191+
}
192+
193+
internal class JavaUuidValueClassUnboxConverter<T : Any>(
194+
override val valueClass: Class<T>,
195+
unboxMethod: Method,
196+
) : ValueClassUnboxConverter<T, UUID?>() {
197+
override val unboxedType: Type get() = UUID::class.java
198+
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_JAVA_UUID_METHOD_TYPE)
199+
200+
override fun convert(value: T): UUID? = unboxHandle.invokeExact(value) as UUID?
201+
}
202+
203+
internal class GenericValueClassUnboxConverter<T : Any>(
204+
override val valueClass: Class<T>,
205+
override val unboxedType: Type,
206+
unboxMethod: Method,
207+
) : ValueClassUnboxConverter<T, Any?>() {
208+
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_ANY_METHOD_TYPE)
209+
210+
override fun convert(value: T): Any? = unboxHandle.invokeExact(value)
62211
}

src/main/kotlin/com/fasterxml/jackson/module/kotlin/InternalCommons.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ package com.fasterxml.jackson.module.kotlin
22

33
import com.fasterxml.jackson.annotation.JsonCreator
44
import com.fasterxml.jackson.databind.JsonMappingException
5+
import java.lang.invoke.MethodHandle
6+
import java.lang.invoke.MethodHandles
7+
import java.lang.invoke.MethodType
58
import java.lang.reflect.AnnotatedElement
9+
import java.lang.reflect.Method
610
import java.util.*
711
import kotlin.reflect.KClass
812
import kotlin.reflect.KType
@@ -46,3 +50,16 @@ internal fun AnnotatedElement.hasCreatorAnnotation(): Boolean = getAnnotation(Js
4650
// Determine if the unbox result of value class is nullable
4751
internal fun KClass<*>.wrapsNullable(): Boolean =
4852
this.memberProperties.first { it.javaField != null }.returnType.isMarkedNullable
53+
54+
internal val ANY_TO_ANY_METHOD_TYPE by lazy {MethodType.methodType(Any::class.java, Any::class.java) }
55+
internal val ANY_TO_INT_METHOD_TYPE by lazy {MethodType.methodType(Int::class.java, Any::class.java) }
56+
internal val ANY_TO_LONG_METHOD_TYPE by lazy {MethodType.methodType(Long::class.java, Any::class.java) }
57+
internal val ANY_TO_STRING_METHOD_TYPE by lazy {MethodType.methodType(String::class.java, Any::class.java) }
58+
internal val ANY_TO_JAVA_UUID_METHOD_TYPE by lazy {MethodType.methodType(UUID::class.java, Any::class.java) }
59+
internal val INT_TO_ANY_METHOD_TYPE by lazy {MethodType.methodType(Any::class.java, Int::class.java) }
60+
internal val LONG_TO_ANY_METHOD_TYPE by lazy {MethodType.methodType(Any::class.java, Long::class.java) }
61+
internal val STRING_TO_ANY_METHOD_TYPE by lazy {MethodType.methodType(Any::class.java, String::class.java) }
62+
internal val JAVA_UUID_TO_ANY_METHOD_TYPE by lazy {MethodType.methodType(Any::class.java, UUID::class.java) }
63+
64+
internal fun unreflect(method: Method): MethodHandle = MethodHandles.lookup().unreflect(method)
65+
internal fun unreflectAsType(method: Method, type: MethodType): MethodHandle = unreflect(method).asType(type)

0 commit comments

Comments
 (0)