Skip to content

Commit ed7bf80

Browse files
authored
Properly cache generated serializers in PluginGeneratedSerialDescript… (#1159)
Properly cache generated serializers in PluginGeneratedSerialDescriptor in order to avoid allocation of nullable serializer and descriptor. Also, slightly reduce bytecode size Fixes #1156
1 parent d271407 commit ed7bf80

File tree

7 files changed

+80
-13
lines changed

7 files changed

+80
-13
lines changed

benchmark/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131
compile 'com.esotericsoftware:kryo:4.0.0'
3232

3333
compile project(':kotlinx-serialization-core')
34+
compile project(':kotlinx-serialization-json')
3435
compile project(':kotlinx-serialization-protobuf')
3536

3637
// async profiler
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
package kotlinx.benchmarks.json
5+
6+
import kotlinx.serialization.*
7+
import kotlinx.serialization.json.*
8+
import org.openjdk.jmh.annotations.*
9+
import java.util.concurrent.*
10+
11+
@Warmup(iterations = 5, time = 1)
12+
@Measurement(iterations = 5, time = 1)
13+
@BenchmarkMode(Mode.Throughput)
14+
@OutputTimeUnit(TimeUnit.MICROSECONDS)
15+
@State(Scope.Benchmark)
16+
@Fork(2)
17+
open class CoerceInputValuesBenchmark {
18+
19+
// Specific benchmark to isolate effect on #1156. Remove after release of 1.0.1
20+
21+
@Serializable
22+
class Holder(
23+
val i1: Int,
24+
val i2: Int,
25+
val i3: Int,
26+
val i4: Int,
27+
val i5: Int,
28+
val i6: Int,
29+
val i7: Int,
30+
val i8: Int,
31+
val i9: Int,
32+
val i10: Int
33+
)
34+
35+
@Serializable
36+
class NullableHolder(
37+
val i1: Int?,
38+
val i2: Int?,
39+
val i3: Int?,
40+
val i4: Int?,
41+
val i5: Int?,
42+
val i6: Int?,
43+
val i7: Int?,
44+
val i8: Int?,
45+
val i9: Int?,
46+
val i10: Int?
47+
)
48+
49+
private val str = """{"i1":1,"i2":1,"i3":1,"i4":1,"i5":1,"i6":1,"i7":1,"i8":1,"i9":1,"i10":1}"""
50+
51+
private val json = Json { coerceInputValues = false }
52+
private val coercingJson = Json { coerceInputValues = true }
53+
54+
@Benchmark
55+
fun testNullableCoercing() = coercingJson.decodeFromString(NullableHolder.serializer(), str)
56+
57+
@Benchmark
58+
fun testNullableRegular() = json.decodeFromString(NullableHolder.serializer(), str)
59+
60+
61+
@Benchmark
62+
fun testNonNullableCoercing() = coercingJson.decodeFromString(Holder.serializer(), str)
63+
64+
@Benchmark
65+
fun testNonNullableRegular() = json.decodeFromString(Holder.serializer(), str)
66+
}

benchmark/src/jmh/kotlin/kotlinx/benchmarks/GeneratedBenchmark.kt renamed to benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/GeneratedBenchmark.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

5-
package kotlinx.benchmarks
5+
package kotlinx.benchmarks.json
66

77
import kotlinx.serialization.*
88
import kotlinx.serialization.json.*

benchmark/src/jmh/kotlin/kotlinx/benchmarks/ProtoBaseline.kt renamed to benchmark/src/jmh/kotlin/kotlinx/benchmarks/protobuf/ProtoBaseline.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
/*
22
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
4+
package kotlinx.benchmarks.protobuf
45

5-
package kotlinx.benchmarks
6-
7-
import kotlinx.serialization.*
6+
import kotlinx.serialization.Serializable
87
import kotlinx.serialization.protobuf.*
98
import org.openjdk.jmh.annotations.*
109
import java.util.concurrent.*

benchmark/src/jmh/kotlin/kotlinx/benchmarks/ProtoListBenchmark.kt renamed to benchmark/src/jmh/kotlin/kotlinx/benchmarks/protobuf/ProtoListBenchmark.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
22
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
4-
5-
package kotlinx.benchmarks
4+
package kotlinx.benchmarks.protobuf
65

76
import kotlinx.serialization.*
87
import kotlinx.serialization.protobuf.*
8+
import kotlinx.serialization.protobuf.ProtoBuf.Default.encodeToByteArray
99
import org.openjdk.jmh.annotations.*
1010
import java.util.concurrent.*
1111

benchmark/src/jmh/kotlin/kotlinx/benchmarks/ProtoListLikeBenchmark.kt renamed to benchmark/src/jmh/kotlin/kotlinx/benchmarks/protobuf/ProtoListLikeBenchmark.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
/*
22
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
4-
5-
package kotlinx.benchmarks
4+
package kotlinx.benchmarks.protobuf
65

76
import kotlinx.serialization.*
87
import kotlinx.serialization.protobuf.*

core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ internal open class PluginGeneratedSerialDescriptor(
2828

2929
// Classes rarely have annotations, so we can save up a bit of allocations here
3030
private var classAnnotations: MutableList<Annotation>? = null
31-
private var elementsOptionality = BooleanArray(elementsCount)
31+
private val elementsOptionality = BooleanArray(elementsCount)
3232
internal val namesSet: Set<String> get() = indices.keys
3333

3434
// don't change lazy mode: KT-32871, KT-32872
3535
private val indices: Map<String, Int> by lazy { buildIndices() }
36+
// Cache child serializers, they are not cached by the implementation for nullable types
37+
private val childSerializers by lazy { generatedSerializer?.childSerializers() ?: emptyArray() }
3638

3739
// Lazy because of JS specific initialization order (#789)
3840
private val typeParameterDescriptors: Array<SerialDescriptor> by lazy {
@@ -69,8 +71,7 @@ internal open class PluginGeneratedSerialDescriptor(
6971
}
7072

7173
override fun getElementDescriptor(index: Int): SerialDescriptor {
72-
return generatedSerializer?.childSerializers()?.get(index)?.descriptor
73-
?: throw IndexOutOfBoundsException("$serialName descriptor has only $elementsCount elements, index: $index")
74+
return childSerializers.getChecked(index).descriptor
7475
}
7576

7677
override fun isElementOptional(index: Int): Boolean = elementsOptionality.getChecked(index)
@@ -100,6 +101,7 @@ internal open class PluginGeneratedSerialDescriptor(
100101
}
101102
}
102103

104+
@OptIn(ExperimentalSerializationApi::class)
103105
internal inline fun <reified SD : SerialDescriptor> SD.equalsImpl(
104106
other: Any?,
105107
typeParamsAreEqual: (otherDescriptor: SD) -> Boolean
@@ -116,8 +118,8 @@ internal inline fun <reified SD : SerialDescriptor> SD.equalsImpl(
116118
return true
117119
}
118120

119-
@Suppress("NOTHING_TO_INLINE")
120-
internal inline fun SerialDescriptor.hashCodeImpl(typeParams: Array<SerialDescriptor>): Int {
121+
@OptIn(ExperimentalSerializationApi::class)
122+
internal fun SerialDescriptor.hashCodeImpl(typeParams: Array<SerialDescriptor>): Int {
121123
var result = serialName.hashCode()
122124
result = 31 * result + typeParams.contentHashCode()
123125
val elementDescriptors = elementDescriptors

0 commit comments

Comments
 (0)