Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
sandwwraith committed Oct 13, 2022
2 parents 0a1b6d8 + daa95c7 commit de6864a
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -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)

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ open class PolymorphismOverheadBenchmark {
@JsonClassDiscriminator("poly")
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)

Expand All @@ -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)
Expand All @@ -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)

}
Original file line number Diff line number Diff line change
@@ -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)

}
10 changes: 5 additions & 5 deletions docs/polymorphism.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)`.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit de6864a

Please sign in to comment.