summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeonid Startsev <sandwwraith@gmail.com>2022-10-13 17:55:28 +0200
committerLeonid Startsev <sandwwraith@gmail.com>2022-10-13 17:55:28 +0200
commitde6864af7039ab91dac4f0764af94c96da018914 (patch)
tree92c5590d771465b4fab342a669d6ac09c180b579
parent0a1b6d856da3bc9e6a19f77ad66c1b241533a20b (diff)
parentdaa95c79ffadc0eedbbb4a481a00556b78212e43 (diff)
downloadkotlinx.serialization-de6864af7039ab91dac4f0764af94c96da018914.tar.gz
Merge remote-tracking branch 'origin/master' into dev
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt72
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt16
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt123
-rw-r--r--docs/polymorphism.md10
-rw-r--r--formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt2
5 files changed, 217 insertions, 6 deletions
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt
new file mode 100644
index 00000000..2773cfc2
--- /dev/null
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt
@@ -0,0 +1,72 @@
+package kotlinx.benchmarks.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.buildClassSerialDescriptor
+import kotlinx.serialization.descriptors.element
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+@Warmup(iterations = 7, time = 1)
+@Measurement(iterations = 5, time = 1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+@Fork(1)
+open class ContextualOverheadBenchmark {
+ @Serializable
+ data class Holder(val data: @Contextual Data)
+
+ class Data(val a: Int, val b: String)
+
+ object DataSerializer: KSerializer<Data> {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Serializer") {
+ element<Int>("a")
+ element<String>("b")
+ }
+
+ override fun deserialize(decoder: Decoder): Data {
+ return decoder.decodeStructure(descriptor) {
+ var a = 0
+ var b = ""
+ while (true) {
+ when (val index = decodeElementIndex(descriptor)) {
+ 0 -> a = decodeIntElement(descriptor, 0)
+ 1 -> b = decodeStringElement(descriptor, 1)
+ CompositeDecoder.DECODE_DONE -> break
+ else -> error("Unexpected index: $index")
+ }
+ }
+ Data(a, b)
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: Data) {
+ encoder.encodeStructure(descriptor) {
+ encodeIntElement(descriptor, 0, value.a)
+ encodeStringElement(descriptor, 1, value.b)
+ }
+ }
+
+ }
+
+ private val module = SerializersModule {
+ contextual(DataSerializer)
+ }
+
+ private val json = Json { serializersModule = module }
+
+ private val holder = Holder(Data(1, "abc"))
+ private val holderString = json.encodeToString(holder)
+ private val holderSerializer = serializer<Holder>()
+
+ @Benchmark
+ fun decode() = json.decodeFromString(holderSerializer, holderString)
+
+ @Benchmark
+ fun encode() = json.encodeToString(holderSerializer, holder)
+
+}
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt
index b272bae6..9af856d3 100644
--- a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt
@@ -19,6 +19,9 @@ open class PolymorphismOverheadBenchmark {
data class PolymorphicWrapper(val i: @Polymorphic Poly, val i2: Impl) // amortize the cost a bit
@Serializable
+ data class SimpleWrapper(val poly: @Polymorphic Poly)
+
+ @Serializable
data class BaseWrapper(val i: Impl, val i2: Impl)
@JsonClassDiscriminator("poly")
@@ -40,6 +43,11 @@ open class PolymorphismOverheadBenchmark {
private val polyString = json.encodeToString<Poly>(impl)
private val serializer = serializer<Poly>()
+ private val wrapper = SimpleWrapper(Impl(1, "abc"))
+ private val wrapperString = json.encodeToString(wrapper)
+ private val wrapperSerializer = serializer<SimpleWrapper>()
+
+
// 5000
@Benchmark
fun base() = json.decodeFromString(Impl.serializer(), implString)
@@ -51,4 +59,12 @@ open class PolymorphismOverheadBenchmark {
@Benchmark
fun poly() = json.decodeFromString(serializer, polyString)
+ // test for child polymorphic serializer in decoding
+ @Benchmark
+ fun polyChildDecode() = json.decodeFromString(wrapperSerializer, wrapperString)
+
+ // test for child polymorphic serializer in encoding
+ @Benchmark
+ fun polyChildEncode() = json.encodeToString(wrapperSerializer, wrapper)
+
}
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt
new file mode 100644
index 00000000..bc3c89d7
--- /dev/null
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt
@@ -0,0 +1,123 @@
+@file:UseSerializers(UseSerializerOverheadBenchmark.DataClassSerializer::class, UseSerializerOverheadBenchmark.DataObjectSerializer::class)
+package kotlinx.benchmarks.json
+
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.buildClassSerialDescriptor
+import kotlinx.serialization.descriptors.element
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+@Warmup(iterations = 7, time = 1)
+@Measurement(iterations = 5, time = 1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+@Fork(1)
+open class UseSerializerOverheadBenchmark {
+ @Serializable
+ data class HolderForClass(val data: DataForClass)
+
+ @Serializable
+ data class HolderForObject(val data: DataForObject)
+
+ class DataForClass(val a: Int, val b: String)
+
+ class DataForObject(val a: Int, val b: String)
+
+ object DataClassSerializer: KSerializer<DataForClass> {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ClassSerializer") {
+ element<Int>("a")
+ element<String>("b")
+ }
+
+ override fun deserialize(decoder: Decoder): DataForClass {
+ return decoder.decodeStructure(descriptor) {
+ var a = 0
+ var b = ""
+ while (true) {
+ when (val index = decodeElementIndex(ContextualOverheadBenchmark.DataSerializer.descriptor)) {
+ 0 -> a = decodeIntElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 0)
+ 1 -> b = decodeStringElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 1)
+ CompositeDecoder.DECODE_DONE -> break
+ else -> error("Unexpected index: $index")
+ }
+ }
+ DataForClass(a, b)
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: DataForClass) {
+ encoder.encodeStructure(descriptor) {
+ encodeIntElement(descriptor, 0, value.a)
+ encodeStringElement(descriptor, 1, value.b)
+ }
+ }
+ }
+
+ object DataObjectSerializer: KSerializer<DataForObject> {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ObjectSerializer") {
+ element<Int>("a")
+ element<String>("b")
+ }
+
+ override fun deserialize(decoder: Decoder): DataForObject {
+ return decoder.decodeStructure(descriptor) {
+ var a = 0
+ var b = ""
+ while (true) {
+ when (val index = decodeElementIndex(ContextualOverheadBenchmark.DataSerializer.descriptor)) {
+ 0 -> a = decodeIntElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 0)
+ 1 -> b = decodeStringElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 1)
+ CompositeDecoder.DECODE_DONE -> break
+ else -> error("Unexpected index: $index")
+ }
+ }
+ DataForObject(a, b)
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: DataForObject) {
+ encoder.encodeStructure(descriptor) {
+ encodeIntElement(descriptor, 0, value.a)
+ encodeStringElement(descriptor, 1, value.b)
+ }
+ }
+ }
+
+ private val module = SerializersModule {
+ contextual(DataClassSerializer)
+ }
+
+ private val json = Json { serializersModule = module }
+
+ private val classHolder = HolderForClass(DataForClass(1, "abc"))
+ private val classHolderString = json.encodeToString(classHolder)
+ private val classHolderSerializer = serializer<HolderForClass>()
+
+ private val objectHolder = HolderForObject(DataForObject(1, "abc"))
+ private val objectHolderString = json.encodeToString(objectHolder)
+ private val objectHolderSerializer = serializer<HolderForObject>()
+
+ @Benchmark
+ fun decodeForClass() = json.decodeFromString(classHolderSerializer, classHolderString)
+
+ @Benchmark
+ fun encodeForClass() = json.encodeToString(classHolderSerializer, classHolder)
+
+ /*
+ Any optimizations should not affect the speed of these tests.
+ It doesn't make sense to cache singleton (`object`) serializer, because the object is accessed instantly
+ */
+
+ @Benchmark
+ fun decodeForObject() = json.decodeFromString(objectHolderSerializer, objectHolderString)
+
+ @Benchmark
+ fun encodeForObject() = json.encodeToString(objectHolderSerializer, objectHolder)
+
+}
diff --git a/docs/polymorphism.md b/docs/polymorphism.md
index f49c0ed6..486c7489 100644
--- a/docs/polymorphism.md
+++ b/docs/polymorphism.md
@@ -41,11 +41,11 @@ Let us start with basic introduction to polymorphism.
### Static types
-Kotlin serialization is fully static with respect to types by default. The structure of encoded objects is determined
+Kotlin Serialization is fully static with respect to types by default. The structure of encoded objects is determined
by *compile-time* types of objects. Let's examine this aspect in more detail and learn how
to serialize polymorphic data structures, where the type of data is determined at runtime.
-To show the static nature of Kotlin serialization let us make the following setup. An `open class Project`
+To show the static nature of Kotlin Serialization let us make the following setup. An `open class Project`
has just the `name` property, while its derived `class OwnedProject` adds an `owner` property.
In the below example, we serialize `data` variable with a static type of
`Project` that is initialized with an instance of `OwnedProject` at runtime.
@@ -194,7 +194,7 @@ discriminator property is not emitted into the resulting JSON.
<!--- TEST -->
-In general, Kotlin serialization is designed to work correctly only when the compile-time type used during serialization
+In general, Kotlin Serialization is designed to work correctly only when the compile-time type used during serialization
is the same one as the compile-time type used during deserialization. You can always specify the type explicitly
when calling serialization functions. The previous example can be corrected to use `Project` type for serialization
by calling `Json.encodeToString<Project>(data)`.
@@ -592,7 +592,7 @@ With the explicit serializer it works as before.
### Explicitly marking polymorphic class properties
The property of an interface type is implicitly considered polymorphic, since interfaces are all about runtime polymorphism.
-However, Kotlin serialization does not compile a serializable class with a property of a non-serializable class type.
+However, Kotlin Serialization does not compile a serializable class with a property of a non-serializable class type.
If we have a property of `Any` class or other non-serializable class, then we must explicitly provide its serialization
strategy via the [`@Serializable`][Serializable] annotation as we saw in
the [Specifying serializer on a property](serializers.md#specifying-serializer-on-a-property) section.
@@ -708,7 +708,7 @@ abstract class Response<out T>
data class OkResponse<out T>(val data: T) : Response<T>()
```
-Kotlin serialization does not have a builtin strategy to represent the actually provided argument type for the
+Kotlin Serialization does not have a builtin strategy to represent the actually provided argument type for the
type parameter `T` when serializing a property of the polymorphic type `OkResponse<T>`. We have to provide this
strategy explicitly when defining the serializers module for the `Response`. In the below example we
use `OkResponse.serializer(...)` to retrieve
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt
index 4bd46d59..e5815838 100644
--- a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt
+++ b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt
@@ -257,7 +257,7 @@ private class DynamicPrimitiveEncoder(
if (!json.configuration.isLenient && abs(value) > MAX_SAFE_INTEGER) {
throw IllegalArgumentException(
"$value can't be deserialized to number due to a potential precision loss. " +
- "Use the JsonConfiguration option isLenient to serialise anyway"
+ "Use the JsonConfiguration option isLenient to serialize anyway"
)
}
encodeValue(asDouble)