Skip to content

Commit 2825e21

Browse files
authored
Implemented serializers caching for lookup
1 parent 49dad86 commit 2825e21

File tree

10 files changed

+424
-53
lines changed

10 files changed

+424
-53
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.benchmarks.json
6+
7+
import kotlinx.serialization.*
8+
import kotlinx.serialization.builtins.*
9+
import kotlinx.serialization.json.*
10+
import org.openjdk.jmh.annotations.*
11+
import java.util.concurrent.*
12+
13+
@Warmup(iterations = 7, time = 1)
14+
@Measurement(iterations = 7, time = 1)
15+
@BenchmarkMode(Mode.Throughput)
16+
@OutputTimeUnit(TimeUnit.MICROSECONDS)
17+
@State(Scope.Benchmark)
18+
@Fork(2)
19+
open class LookupOverheadBenchmark {
20+
21+
@Serializable
22+
class Holder(val a: String)
23+
24+
@Serializable
25+
class Generic<T>(val a: T)
26+
27+
@Serializable
28+
class DoubleGeneric<T1, T2>(val a: T1, val b: T2)
29+
30+
@Serializable
31+
class PentaGeneric<T1, T2, T3, T4, T5>(val a: T1, val b: T2, val c: T3, val d: T4, val e: T5)
32+
33+
private val data = """{"a":""}"""
34+
private val doubleData = """{"a":"","b":0}"""
35+
private val pentaData = """{"a":"","b":0,"c":1,"d":true,"e":" "}"""
36+
37+
@Serializable
38+
object Object
39+
40+
@Benchmark
41+
fun dataReified() = Json.decodeFromString<Holder>(data)
42+
43+
@Benchmark
44+
fun dataPlain() = Json.decodeFromString(Holder.serializer(), data)
45+
46+
@Benchmark
47+
fun genericReified() = Json.decodeFromString<Generic<String>>(data)
48+
49+
@Benchmark
50+
fun genericPlain() = Json.decodeFromString(Generic.serializer(String.serializer()), data)
51+
52+
@Benchmark
53+
fun doubleGenericReified() = Json.decodeFromString<DoubleGeneric<String, Int>>(doubleData)
54+
55+
@Benchmark
56+
fun doubleGenericPlain() = Json.decodeFromString(DoubleGeneric.serializer(String.serializer(), Int.serializer()), doubleData)
57+
58+
@Benchmark
59+
fun pentaGenericReified() = Json.decodeFromString<PentaGeneric<String, Int, Long, Boolean, Char>>(pentaData)
60+
61+
@Benchmark
62+
fun pentaGenericPlain() = Json.decodeFromString(PentaGeneric.serializer(String.serializer(), Int.serializer(), Long.serializer(), Boolean.serializer(), Char.serializer()), pentaData)
63+
64+
@Benchmark
65+
fun objectReified() = Json.decodeFromString<Object>("{}")
66+
67+
@Benchmark
68+
fun objectPlain() = Json.decodeFromString(Object.serializer(), "{}")
69+
}

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ subprojects {
173173
afterEvaluate { // Can be applied only when the project is evaluated
174174
animalsniffer {
175175
sourceSets = [sourceSets.main]
176-
annotation = "kotlinx.serialization.json.internal.SuppressAnimalSniffer"
176+
annotation = (name == "kotlinx-serialization-core")? "kotlinx.serialization.internal.SuppressAnimalSniffer" : "kotlinx.serialization.json.internal.SuppressAnimalSniffer"
177177
}
178178
dependencies {
179179
signature 'net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature'

core/commonMain/src/kotlinx/serialization/Serializers.kt

Lines changed: 78 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,8 @@ public fun SerializersModule.serializer(type: KType): KSerializer<Any?> =
6666
* Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable and is not registered in [this] module).
6767
*/
6868
@OptIn(ExperimentalSerializationApi::class)
69-
public fun SerializersModule.serializerOrNull(type: KType): KSerializer<Any?>? {
70-
return serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false)
71-
}
69+
public fun SerializersModule.serializerOrNull(type: KType): KSerializer<Any?>? =
70+
serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false)
7271

7372
@OptIn(ExperimentalSerializationApi::class)
7473
private fun SerializersModule.serializerByKTypeImpl(
@@ -79,54 +78,47 @@ private fun SerializersModule.serializerByKTypeImpl(
7978
val isNullable = type.isMarkedNullable
8079
val typeArguments = type.arguments
8180
.map { requireNotNull(it.type) { "Star projections in type arguments are not allowed, but had $type" } }
82-
val result: KSerializer<Any>? = when {
83-
typeArguments.isEmpty() -> rootClass.serializerOrNull() ?: getContextual(rootClass)
84-
else -> builtinSerializer(typeArguments, rootClass, failOnMissingTypeArgSerializer)
85-
}?.cast()
86-
return result?.nullable(isNullable)
87-
}
8881

89-
@OptIn(ExperimentalSerializationApi::class)
90-
private fun SerializersModule.builtinSerializer(
91-
typeArguments: List<KType>,
92-
rootClass: KClass<Any>,
93-
failOnMissingTypeArgSerializer: Boolean
94-
): KSerializer<out Any>? {
95-
val serializers = if (failOnMissingTypeArgSerializer)
96-
typeArguments.map(::serializer)
97-
else {
98-
typeArguments.map { serializerOrNull(it) ?: return null }
82+
val cachedSerializer = if (typeArguments.isEmpty()) {
83+
findCachedSerializer(rootClass, isNullable)
84+
} else {
85+
val cachedResult = findParametrizedCachedSerializer(rootClass, typeArguments, isNullable)
86+
if (failOnMissingTypeArgSerializer) {
87+
cachedResult.getOrNull()
88+
} else {
89+
// return null if error occurred - serializer for parameter(s) was not found
90+
cachedResult.getOrElse { return null }
91+
}
9992
}
100-
// Array is not supported, see KT-32839
101-
return when (rootClass) {
102-
Collection::class, List::class, MutableList::class, ArrayList::class -> ArrayListSerializer(serializers[0])
103-
HashSet::class -> HashSetSerializer(serializers[0])
104-
Set::class, MutableSet::class, LinkedHashSet::class -> LinkedHashSetSerializer(serializers[0])
105-
HashMap::class -> HashMapSerializer(serializers[0], serializers[1])
106-
Map::class, MutableMap::class, LinkedHashMap::class -> LinkedHashMapSerializer(
107-
serializers[0],
108-
serializers[1]
93+
cachedSerializer?.let { return it }
94+
95+
// slow path to find contextual serializers in serializers module
96+
val contextualSerializer: KSerializer<out Any?>? = if (typeArguments.isEmpty()) {
97+
getContextual(rootClass)
98+
} else {
99+
val serializers = serializersForParameters(typeArguments, failOnMissingTypeArgSerializer) ?: return null
100+
// first, we look among the built-in serializers, because the parameter could be contextual
101+
rootClass.parametrizedSerializerOrNull(typeArguments, serializers) ?: getContextual(
102+
rootClass,
103+
serializers
109104
)
110-
Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1])
111-
Pair::class -> PairSerializer(serializers[0], serializers[1])
112-
Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2])
113-
else -> {
114-
if (isReferenceArray(rootClass)) {
115-
return ArraySerializer<Any, Any?>(typeArguments[0].classifier as KClass<Any>, serializers[0]).cast()
116-
}
117-
val args = serializers.toTypedArray()
118-
rootClass.constructSerializerForGivenTypeArgs(*args)
119-
?: reflectiveOrContextual(rootClass, serializers)
120-
}
121105
}
106+
return contextualSerializer?.cast<Any>()?.nullable(isNullable)
122107
}
123108

124-
@OptIn(ExperimentalSerializationApi::class)
125-
internal fun <T : Any> SerializersModule.reflectiveOrContextual(
126-
kClass: KClass<T>,
127-
typeArgumentsSerializers: List<KSerializer<Any?>>
128-
): KSerializer<T>? {
129-
return kClass.serializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers)
109+
/**
110+
* Returns null only if `failOnMissingTypeArgSerializer == false` and at least one parameter serializer not found.
111+
*/
112+
internal fun SerializersModule.serializersForParameters(
113+
typeArguments: List<KType>,
114+
failOnMissingTypeArgSerializer: Boolean
115+
): List<KSerializer<Any?>>? {
116+
val serializers = if (failOnMissingTypeArgSerializer) {
117+
typeArguments.map { serializer(it) }
118+
} else {
119+
typeArguments.map { serializerOrNull(it) ?: return null }
120+
}
121+
return serializers
130122
}
131123

132124
/**
@@ -179,6 +171,47 @@ public fun <T : Any> KClass<T>.serializer(): KSerializer<T> = serializerOrNull()
179171
public fun <T : Any> KClass<T>.serializerOrNull(): KSerializer<T>? =
180172
compiledSerializerImpl() ?: builtinSerializerOrNull()
181173

174+
internal fun KClass<Any>.parametrizedSerializerOrNull(
175+
types: List<KType>,
176+
serializers: List<KSerializer<Any?>>
177+
): KSerializer<out Any>? {
178+
// builtin first because some standard parametrized interfaces (e.g. Map) must use builtin serializer but not polymorphic
179+
return builtinParametrizedSerializer(types, serializers) ?: compiledParametrizedSerializer(serializers)
180+
}
181+
182+
183+
private fun KClass<Any>.compiledParametrizedSerializer(serializers: List<KSerializer<Any?>>): KSerializer<out Any>? {
184+
return constructSerializerForGivenTypeArgs(*serializers.toTypedArray())
185+
}
186+
187+
@OptIn(ExperimentalSerializationApi::class)
188+
private fun KClass<Any>.builtinParametrizedSerializer(
189+
typeArguments: List<KType>,
190+
serializers: List<KSerializer<Any?>>,
191+
): KSerializer<out Any>? {
192+
// Array is not supported, see KT-32839
193+
return when (this) {
194+
Collection::class, List::class, MutableList::class, ArrayList::class -> ArrayListSerializer(serializers[0])
195+
HashSet::class -> HashSetSerializer(serializers[0])
196+
Set::class, MutableSet::class, LinkedHashSet::class -> LinkedHashSetSerializer(serializers[0])
197+
HashMap::class -> HashMapSerializer(serializers[0], serializers[1])
198+
Map::class, MutableMap::class, LinkedHashMap::class -> LinkedHashMapSerializer(
199+
serializers[0],
200+
serializers[1]
201+
)
202+
Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1])
203+
Pair::class -> PairSerializer(serializers[0], serializers[1])
204+
Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2])
205+
else -> {
206+
if (isReferenceArray(this)) {
207+
ArraySerializer(typeArguments[0].classifier as KClass<Any>, serializers[0])
208+
} else {
209+
null
210+
}
211+
}
212+
}
213+
}
214+
182215
private fun <T : Any> KSerializer<T>.nullable(shouldBeNullable: Boolean): KSerializer<T?> {
183216
if (shouldBeNullable) return nullable
184217
return this as KSerializer<T?>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.serialization
6+
7+
import kotlinx.serialization.builtins.nullable
8+
import kotlinx.serialization.internal.cast
9+
import kotlinx.serialization.internal.createCache
10+
import kotlinx.serialization.internal.createParametrizedCache
11+
import kotlinx.serialization.modules.EmptySerializersModule
12+
import kotlin.native.concurrent.ThreadLocal
13+
import kotlin.reflect.KClass
14+
import kotlin.reflect.KType
15+
16+
17+
/**
18+
* Cache for non-null non-parametrized and non-contextual serializers.
19+
*/
20+
@ThreadLocal
21+
private val SERIALIZERS_CACHE = createCache { it.serializerOrNull() }
22+
23+
/**
24+
* Cache for nullable non-parametrized and non-contextual serializers.
25+
*/
26+
@ThreadLocal
27+
private val SERIALIZERS_CACHE_NULLABLE = createCache<Any?> { it.serializerOrNull()?.nullable?.cast() }
28+
29+
/**
30+
* Cache for non-null parametrized and non-contextual serializers.
31+
*/
32+
@ThreadLocal
33+
private val PARAMETRIZED_SERIALIZERS_CACHE = createParametrizedCache { clazz, types ->
34+
val serializers = EmptySerializersModule().serializersForParameters(types, true)!!
35+
clazz.parametrizedSerializerOrNull(types, serializers)
36+
}
37+
38+
/**
39+
* Cache for nullable parametrized and non-contextual serializers.
40+
*/
41+
@ThreadLocal
42+
private val PARAMETRIZED_SERIALIZERS_CACHE_NULLABLE = createParametrizedCache<Any?> { clazz, types ->
43+
val serializers = EmptySerializersModule().serializersForParameters(types, true)!!
44+
clazz.parametrizedSerializerOrNull(types, serializers)?.nullable?.cast()
45+
}
46+
47+
/**
48+
* Find cacheable serializer in the cache.
49+
* If serializer is cacheable but missed in cache - it will be created, placed into the cache and returned.
50+
*/
51+
internal fun findCachedSerializer(clazz: KClass<Any>, isNullable: Boolean): KSerializer<Any?>? {
52+
return if (!isNullable) {
53+
SERIALIZERS_CACHE.get(clazz)?.cast()
54+
} else {
55+
SERIALIZERS_CACHE_NULLABLE.get(clazz)
56+
}
57+
}
58+
59+
/**
60+
* Find cacheable parametrized serializer in the cache.
61+
* If serializer is cacheable but missed in cache - it will be created, placed into the cache and returned.
62+
*/
63+
internal fun findParametrizedCachedSerializer(
64+
clazz: KClass<Any>,
65+
types: List<KType>,
66+
isNullable: Boolean
67+
): Result<KSerializer<Any?>?> {
68+
return if (!isNullable) {
69+
@Suppress("UNCHECKED_CAST")
70+
PARAMETRIZED_SERIALIZERS_CACHE.get(clazz, types) as Result<KSerializer<Any?>?>
71+
} else {
72+
PARAMETRIZED_SERIALIZERS_CACHE_NULLABLE.get(clazz, types)
73+
}
74+
}

core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,18 @@ internal expect fun BooleanArray.getChecked(index: Int): Boolean
130130

131131
internal expect fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>?
132132

133+
/**
134+
* Create serializers cache for non-parametrized and non-contextual serializers.
135+
* The activity and type of cache is determined for a specific platform and a specific environment.
136+
*/
137+
internal expect fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T>
138+
139+
/**
140+
* Create serializers cache for parametrized and non-contextual serializers. Parameters also non-contextual.
141+
* The activity and type of cache is determined for a specific platform and a specific environment.
142+
*/
143+
internal expect fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T>
144+
133145
internal expect fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E>
134146

135147
/**
@@ -145,3 +157,24 @@ internal expect fun Any.isInstanceOf(kclass: KClass<*>): Boolean
145157
internal inline fun <T, K> Iterable<T>.elementsHashCodeBy(selector: (T) -> K): Int {
146158
return fold(1) { hash, element -> 31 * hash + selector(element).hashCode() }
147159
}
160+
161+
/**
162+
* Cache class for non-parametrized and non-contextual serializers.
163+
*/
164+
internal interface SerializerCache<T> {
165+
/**
166+
* Returns cached serializer or `null` if serializer not found.
167+
*/
168+
fun get(key: KClass<Any>): KSerializer<T>?
169+
}
170+
171+
/**
172+
* Cache class for parametrized and non-contextual serializers.
173+
*/
174+
internal interface ParametrizedSerializerCache<T> {
175+
/**
176+
* Returns successful result with cached serializer or `null` if root serializer not found.
177+
* If no serializer was found for the parameters, then result contains an exception.
178+
*/
179+
fun get(key: KClass<Any>, types: List<KType> = emptyList()): Result<KSerializer<T>?>
180+
}

core/jsMain/src/kotlinx/serialization/internal/Platform.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@ internal actual fun BooleanArray.getChecked(index: Int): Boolean {
2020
internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? =
2121
this.constructSerializerForGivenTypeArgs() ?: this.js.asDynamic().Companion?.serializer() as? KSerializer<T>
2222

23+
internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
24+
return object: SerializerCache<T> {
25+
override fun get(key: KClass<Any>): KSerializer<T>? {
26+
return factory(key)
27+
}
28+
}
29+
}
30+
31+
internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
32+
return object: ParametrizedSerializerCache<T> {
33+
override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
34+
return kotlin.runCatching { factory(key, types) }
35+
}
36+
}
37+
}
38+
2339
internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> = toTypedArray()
2440

2541
internal actual fun Any.isInstanceOf(kclass: KClass<*>): Boolean = kclass.isInstance(this)

0 commit comments

Comments
 (0)