-
Notifications
You must be signed in to change notification settings - Fork 624
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Resolves #195
- Loading branch information
Showing
24 changed files
with
683 additions
and
161 deletions.
There are no files selected for viewing
116 changes: 116 additions & 0 deletions
116
benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ImplicitNullsBenchmark.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package kotlinx.benchmarks.json | ||
|
||
import kotlinx.serialization.Serializable | ||
import kotlinx.serialization.decodeFromString | ||
import kotlinx.serialization.encodeToString | ||
import kotlinx.serialization.json.Json | ||
import org.openjdk.jmh.annotations.* | ||
import java.util.concurrent.TimeUnit | ||
|
||
@Warmup(iterations = 5, time = 1) | ||
@Measurement(iterations = 5, time = 1) | ||
@BenchmarkMode(Mode.Throughput) | ||
@OutputTimeUnit(TimeUnit.MILLISECONDS) | ||
@State(Scope.Benchmark) | ||
@Fork(2) | ||
open class ImplicitNullsBenchmark { | ||
|
||
@Serializable | ||
data class Values( | ||
val field0: Int?, | ||
val field1: Int?, | ||
val field2: Int?, | ||
val field3: Int?, | ||
val field4: Int?, | ||
val field5: Int?, | ||
val field6: Int?, | ||
val field7: Int?, | ||
val field8: Int?, | ||
val field9: Int?, | ||
|
||
val field10: Int?, | ||
val field11: Int?, | ||
val field12: Int?, | ||
val field13: Int?, | ||
val field14: Int?, | ||
val field15: Int?, | ||
val field16: Int?, | ||
val field17: Int?, | ||
val field18: Int?, | ||
val field19: Int?, | ||
|
||
val field20: Int?, | ||
val field21: Int?, | ||
val field22: Int?, | ||
val field23: Int?, | ||
val field24: Int?, | ||
val field25: Int?, | ||
val field26: Int?, | ||
val field27: Int?, | ||
val field28: Int?, | ||
val field29: Int?, | ||
|
||
val field30: Int?, | ||
val field31: Int? | ||
) | ||
|
||
|
||
private val jsonImplicitNulls = Json { explicitNulls = false } | ||
|
||
private val valueWithNulls = Values( | ||
null, null, 2, null, null, null, null, null, null, null, | ||
null, null, null, null, 14, null, null, null, null, null, | ||
null, null, null, null, null, null, null, null, null, null, | ||
null, null | ||
) | ||
|
||
|
||
private val jsonWithNulls = """{"field0":null,"field1":null,"field2":2,"field3":null,"field4":null,"field5":null, | ||
|"field6":null,"field7":null,"field8":null,"field9":null,"field10":null,"field11":null,"field12":null, | ||
|"field13":null,"field14":14,"field15":null,"field16":null,"field17":null,"field18":null,"field19":null, | ||
|"field20":null,"field21":null,"field22":null,"field23":null,"field24":null,"field25":null,"field26":null, | ||
|"field27":null,"field28":null,"field29":null,"field30":null,"field31":null}""".trimMargin() | ||
|
||
private val jsonNoNulls = """{"field0":0,"field1":1,"field2":2,"field3":3,"field4":4,"field5":5, | ||
|"field6":6,"field7":7,"field8":8,"field9":9,"field10":10,"field11":11,"field12":12, | ||
|"field13":13,"field14":14,"field15":15,"field16":16,"field17":17,"field18":18,"field19":19, | ||
|"field20":20,"field21":21,"field22":22,"field23":23,"field24":24,"field25":25,"field26":26, | ||
|"field27":27,"field28":28,"field29":29,"field30":30,"field31":31}""".trimMargin() | ||
|
||
private val jsonWithAbsence = """{"field2":2, "field14":14}""" | ||
|
||
@Benchmark | ||
fun decodeNoNulls() { | ||
Json.decodeFromString<Values>(jsonNoNulls) | ||
} | ||
|
||
@Benchmark | ||
fun decodeNoNullsImplicit() { | ||
jsonImplicitNulls.decodeFromString<Values>(jsonNoNulls) | ||
} | ||
|
||
@Benchmark | ||
fun decodeNulls() { | ||
Json.decodeFromString<Values>(jsonWithNulls) | ||
} | ||
|
||
@Benchmark | ||
fun decodeNullsImplicit() { | ||
jsonImplicitNulls.decodeFromString<Values>(jsonWithNulls) | ||
} | ||
|
||
@Benchmark | ||
fun decodeAbsenceImplicit() { | ||
jsonImplicitNulls.decodeFromString<Values>(jsonWithAbsence) | ||
} | ||
|
||
@Benchmark | ||
fun encodeNulls() { | ||
Json.encodeToString(valueWithNulls) | ||
} | ||
|
||
@Benchmark | ||
fun encodeNullsImplicit() { | ||
jsonImplicitNulls.encodeToString(valueWithNulls) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/* | ||
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
package kotlinx.serialization.internal | ||
|
||
import kotlinx.serialization.ExperimentalSerializationApi | ||
import kotlinx.serialization.descriptors.SerialDescriptor | ||
import kotlinx.serialization.encoding.CompositeDecoder | ||
|
||
@OptIn(ExperimentalSerializationApi::class) | ||
@PublishedApi | ||
internal class ElementMarker( | ||
private val descriptor: SerialDescriptor, | ||
private val filter: (SerialDescriptor, Int) -> Boolean | ||
) { | ||
/* | ||
Element decoding marks from given bytes. | ||
The element number is the same as the bit position. | ||
Marks for the lowest 64 elements are always stored in a single Long value, higher elements stores in long array. | ||
*/ | ||
private var lowerMarks: Long | ||
private val highMarksArray: LongArray | ||
|
||
companion object { | ||
val EMPTY_HIGH_MARKS = LongArray(0) | ||
} | ||
|
||
init { | ||
val elementsCount = descriptor.elementsCount | ||
if (elementsCount <= Long.SIZE_BITS) { | ||
lowerMarks = if (elementsCount == Long.SIZE_BITS) { | ||
// number of bits in the mark is equal to the number of fields | ||
0L | ||
} else { | ||
// (1 - elementsCount) bits are always 1 since there are no fields for them | ||
-1L shl elementsCount | ||
} | ||
highMarksArray = EMPTY_HIGH_MARKS | ||
} else { | ||
lowerMarks = 0L | ||
highMarksArray = prepareHighMarksArray(elementsCount) | ||
} | ||
} | ||
|
||
fun mark(index: Int) { | ||
if (index < Long.SIZE_BITS) { | ||
lowerMarks = lowerMarks or (1L shl index) | ||
} else { | ||
val slot = (index / Long.SIZE_BITS) - 1 | ||
val offsetInSlot = index % Long.SIZE_BITS | ||
highMarksArray[slot] = highMarksArray[slot] or (1L shl offsetInSlot) | ||
} | ||
} | ||
|
||
fun nextUnmarkedIndex(): Int { | ||
val elementsCount = descriptor.elementsCount | ||
while (lowerMarks != -1L) { | ||
val index = lowerMarks.inv().countTrailingZeroBits() | ||
lowerMarks = lowerMarks or (1L shl index) | ||
|
||
if (filter(descriptor, index)) { | ||
return index | ||
} | ||
} | ||
|
||
if (elementsCount > Long.SIZE_BITS) { | ||
return nextUnmarkedHighIndex() | ||
} | ||
return CompositeDecoder.DECODE_DONE | ||
} | ||
|
||
private fun prepareHighMarksArray(elementsCount: Int): LongArray { | ||
// (elementsCount - 1) because only one Long value is needed to store 64 fields etc | ||
val slotsCount = (elementsCount - 1) / Long.SIZE_BITS | ||
val elementsInLastSlot = elementsCount % Long.SIZE_BITS | ||
val highMarks = LongArray(slotsCount) | ||
// (elementsCount % Long.SIZE_BITS) == 0 this means that the fields occupy all bits in mark | ||
if (elementsInLastSlot != 0) { | ||
// all marks except the higher are always 0 | ||
highMarks[highMarks.lastIndex] = -1L shl elementsCount | ||
} | ||
return highMarks | ||
} | ||
|
||
private fun nextUnmarkedHighIndex(): Int { | ||
for (slot in highMarksArray.indices) { | ||
// (slot + 1) because first element in high marks has index 64 | ||
val slotOffset = (slot + 1) * Long.SIZE_BITS | ||
// store in a variable so as not to frequently use the array | ||
var slotMarks = highMarksArray[slot] | ||
|
||
while (slotMarks != -1L) { | ||
val indexInSlot = slotMarks.inv().countTrailingZeroBits() | ||
slotMarks = slotMarks or (1L shl indexInSlot) | ||
|
||
val index = slotOffset + indexInSlot | ||
if (filter(descriptor, index)) { | ||
highMarksArray[slot] = slotMarks | ||
return index | ||
} | ||
} | ||
highMarksArray[slot] = slotMarks | ||
} | ||
return CompositeDecoder.DECODE_DONE | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.