Skip to content
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

[DRAFT] Serializers cache #2004

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package kotlinx.benchmarks.json

import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.*
import org.openjdk.jmh.annotations.*
import java.util.concurrent.*

@Warmup(iterations = 7, time = 1)
@Measurement(iterations = 7, time = 1)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
@Fork(2)
open class LookupOverheadBenchmark {

@Serializable
class Holder(val a: String)

@Serializable
class Generic<T>(val a: T)

private val data = """{"a":""}"""

@Serializable
object Object

@Benchmark
fun dataReified() = Json.decodeFromString<Holder>(data)

@Benchmark
fun dataPlain() = Json.decodeFromString(Holder.serializer(), data)

@Benchmark
fun genericReified() = Json.decodeFromString<Generic<String>>(data)

@Benchmark
fun genericPlain() = Json.decodeFromString(Generic.serializer(String.serializer()), data)

@Benchmark
fun objectReified() = Json.decodeFromString<Object>("{}")

@Benchmark
fun objectPlain() = Json.decodeFromString(Object.serializer(), "{}")
}
10 changes: 8 additions & 2 deletions core/commonMain/src/kotlinx/serialization/Serializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,21 @@ private fun SerializersModule.serializerByKTypeImpl(
): KSerializer<Any?>? {
val rootClass = type.kclass()
val isNullable = type.isMarkedNullable
val typeArguments = type.arguments
.map { requireNotNull(it.type) { "Star projections in type arguments are not allowed, but had $type" } }
val typeArguments = mapTypeArguments(type)
val result: KSerializer<Any>? = when {
typeArguments.isEmpty() -> rootClass.serializerOrNull() ?: getContextual(rootClass)
else -> builtinSerializer(typeArguments, rootClass, failOnMissingTypeArgSerializer)
}?.cast()
return result?.nullable(isNullable)
}

private fun mapTypeArguments(type: KType): List<KType> {
val args = type.arguments
if (args.isEmpty()) return emptyList()
return args
.map { requireNotNull(it.type) { "Star projections in type arguments are not allowed, but had $type" } }
}

@OptIn(ExperimentalSerializationApi::class)
private fun SerializersModule.builtinSerializer(
typeArguments: List<KType>,
Expand Down
31 changes: 27 additions & 4 deletions core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ package kotlinx.serialization.internal

import kotlinx.serialization.*
import java.lang.reflect.*
import java.util.WeakHashMap
import java.util.concurrent.locks.ReadWriteLock
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.*
import kotlin.reflect.*

@Suppress("NOTHING_TO_INLINE")
Expand Down Expand Up @@ -39,8 +43,27 @@ internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vara
return java.constructSerializerForGivenTypeArgs(*args)
}

private val adhocCache = WeakHashMap<Class<*>, KSerializer<Any?>>()
private val rwLock = ReentrantReadWriteLock()

@Suppress("UNCHECKED_CAST")
internal fun <T : Any> Class<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? {
if (args.isNotEmpty()) return constructSerializer(args)

rwLock.read {
var serializer = adhocCache[this]
if (serializer != null) return serializer.cast()
// TODO avoid var, properly cache 'null', properly handle nonempty args
serializer = constructSerializer(args)?.cast()
rwLock.write {
adhocCache[this] = serializer
}
return serializer?.cast()
}
}

@Suppress("UNCHECKED_CAST")
internal fun <T: Any> Class<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? {
private fun <T : Any> Class<T>.constructSerializer(args: Array<out KSerializer<Any?>>): KSerializer<T>? {
if (isEnum && isNotAnnotated()) {
return createEnumSerializer()
}
Expand All @@ -64,15 +87,15 @@ internal fun <T: Any> Class<T>.constructSerializerForGivenTypeArgs(vararg args:
return polymorphicSerializer()
}

private fun <T: Any> Class<T>.isNotAnnotated(): Boolean {
private fun <T : Any> Class<T>.isNotAnnotated(): Boolean {
/*
* For annotated enums search serializer directly (or do not search at all?)
*/
return getAnnotation(Serializable::class.java) == null &&
getAnnotation(Polymorphic::class.java) == null
}

private fun <T: Any> Class<T>.polymorphicSerializer(): KSerializer<T>? {
private fun <T : Any> Class<T>.polymorphicSerializer(): KSerializer<T>? {
/*
* Last resort: check for @Polymorphic or Serializable(with = PolymorphicSerializer::class)
* annotations.
Expand All @@ -87,7 +110,7 @@ private fun <T: Any> Class<T>.polymorphicSerializer(): KSerializer<T>? {
return null
}

private fun <T: Any> Class<T>.interfaceSerializer(): KSerializer<T>? {
private fun <T : Any> Class<T>.interfaceSerializer(): KSerializer<T>? {
/*
* Interfaces are @Polymorphic by default.
* Check if it has no annotations or `@Serializable(with = PolymorphicSerializer::class)`,
Expand Down