From 6b85e136d7a37e50b291475faf6c0f9ea198c8d0 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 18 Nov 2020 18:10:22 +0300 Subject: [PATCH] Properly cache serial names in order to improve performance of JSON parser with strict mode --- core/api/kotlinx-serialization-core.api | 3 ++- .../descriptors/SerialDescriptors.kt | 5 +++-- .../serialization/internal/CachedNames.kt | 20 +++++++++++++++++++ .../internal/NullableSerializer.kt | 6 +++++- .../serialization/internal/Platform.common.kt | 4 ++-- .../PluginGeneratedSerialDescriptor.kt | 4 ++-- 6 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 core/commonMain/src/kotlinx/serialization/internal/CachedNames.kt diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api index 1d9ba1a58..903af3626 100644 --- a/core/api/kotlinx-serialization-core.api +++ b/core/api/kotlinx-serialization-core.api @@ -858,7 +858,7 @@ public final class kotlinx/serialization/internal/Platform_commonKt { public static final fun cast (Lkotlinx/serialization/SerializationStrategy;)Lkotlinx/serialization/SerializationStrategy; } -public class kotlinx/serialization/internal/PluginGeneratedSerialDescriptor : kotlinx/serialization/descriptors/SerialDescriptor { +public class kotlinx/serialization/internal/PluginGeneratedSerialDescriptor : kotlinx/serialization/descriptors/SerialDescriptor, kotlinx/serialization/internal/CachedNames { public fun (Ljava/lang/String;Lkotlinx/serialization/internal/GeneratedSerializer;I)V public synthetic fun (Ljava/lang/String;Lkotlinx/serialization/internal/GeneratedSerializer;IILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addElement (Ljava/lang/String;Z)V @@ -872,6 +872,7 @@ public class kotlinx/serialization/internal/PluginGeneratedSerialDescriptor : ko public final fun getElementsCount ()I public fun getKind ()Lkotlinx/serialization/descriptors/SerialKind; public fun getSerialName ()Ljava/lang/String; + public fun getSerialNames ()Ljava/util/Set; public fun hashCode ()I public fun isElementOptional (I)Z public fun isNullable ()Z diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt index f8c6e137f..f5f9d3a83 100644 --- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt +++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt @@ -270,9 +270,10 @@ internal class SerialDescriptorImpl( override val elementsCount: Int, typeParameters: List, builder: ClassSerialDescriptorBuilder -) : SerialDescriptor { +) : SerialDescriptor, CachedNames { - public override val annotations: List = builder.annotations + override val annotations: List = builder.annotations + override val serialNames: Set = builder.elementNames.toHashSet() private val elementNames: Array = builder.elementNames.toTypedArray() private val elementDescriptors: Array = builder.elementDescriptors.compactArray() diff --git a/core/commonMain/src/kotlinx/serialization/internal/CachedNames.kt b/core/commonMain/src/kotlinx/serialization/internal/CachedNames.kt new file mode 100644 index 000000000..a5b7dfde0 --- /dev/null +++ b/core/commonMain/src/kotlinx/serialization/internal/CachedNames.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.serialization.internal + +import kotlinx.serialization.descriptors.* + +/** + * Internal interface used as a marker for [SerialDescriptor] in order + * to retrieve the set of all element names without allocations. + * Used by our implementations as a performance optimization. + * It's not an instance of [SerialDescriptor] to simplify implementation via delegation + */ +internal interface CachedNames { + + /** + * A set of all names retrieved from [SerialDescriptor.getElementName] + */ + public val serialNames: Set +} diff --git a/core/commonMain/src/kotlinx/serialization/internal/NullableSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/NullableSerializer.kt index ee49f769b..73a4c1afd 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/NullableSerializer.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/NullableSerializer.kt @@ -44,8 +44,12 @@ internal class NullableSerializer(private val serializer: KSerializer = original.cachedSerialNames() override val isNullable: Boolean get() = true diff --git a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt index c13ae956a..a62d11250 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt @@ -6,7 +6,6 @@ package kotlinx.serialization.internal import kotlinx.serialization.* import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* import kotlin.native.concurrent.* import kotlin.reflect.* @@ -56,8 +55,9 @@ internal object InternalHexConverter { } } +@OptIn(ExperimentalSerializationApi::class) internal fun SerialDescriptor.cachedSerialNames(): Set { - if (this is PluginGeneratedSerialDescriptor) return namesSet + if (this is CachedNames) return serialNames val result = HashSet(elementsCount) for (i in 0 until elementsCount) { result += getElementName(i) diff --git a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt index 5572aaa1d..cc6758a88 100644 --- a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt +++ b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt @@ -18,7 +18,7 @@ internal open class PluginGeneratedSerialDescriptor( override val serialName: String, private val generatedSerializer: GeneratedSerializer<*>? = null, final override val elementsCount: Int -) : SerialDescriptor { +) : SerialDescriptor, CachedNames { override val kind: SerialKind get() = StructureKind.CLASS override val annotations: List get() = classAnnotations ?: emptyList() @@ -29,7 +29,7 @@ internal open class PluginGeneratedSerialDescriptor( // Classes rarely have annotations, so we can save up a bit of allocations here private var classAnnotations: MutableList? = null private val elementsOptionality = BooleanArray(elementsCount) - internal val namesSet: Set get() = indices.keys + public override val serialNames: Set get() = indices.keys // don't change lazy mode: KT-32871, KT-32872 private val indices: Map by lazy { buildIndices() }