diff --git a/serialization-msgpack/build.gradle.kts b/serialization-msgpack/build.gradle.kts index 61dbeed..a6c2d77 100644 --- a/serialization-msgpack/build.gradle.kts +++ b/serialization-msgpack/build.gradle.kts @@ -14,6 +14,9 @@ kotlin { } } js { + compilations.create("benchmark") { + associateWith(this@js.compilations.getByName("main")) + } browser { testTask { useKarma { @@ -21,6 +24,7 @@ kotlin { } } } + nodejs {} } applyDefaultHierarchyTemplate() iosArm64() @@ -76,11 +80,18 @@ kotlin { implementation(kotlin("test-js")) } } + val jsBenchmark by getting { + dependencies { + implementation(kotlinx("benchmark-runtime", Dependencies.Versions.benchmark)) + implementation(npm("@msgpack/msgpack", ">2.0.0 <3.0.0")) + } + } } } benchmark { targets { register("jvmBenchmark") + register("jsBenchmark") } } diff --git a/serialization-msgpack/src/jsBenchmark/kotlin/com.ensarsarajcic.kotlinx.serialization.msgpack/DeserializeBenchmarks.kt b/serialization-msgpack/src/jsBenchmark/kotlin/com.ensarsarajcic.kotlinx.serialization.msgpack/DeserializeBenchmarks.kt new file mode 100644 index 0000000..3b89518 --- /dev/null +++ b/serialization-msgpack/src/jsBenchmark/kotlin/com.ensarsarajcic.kotlinx.serialization.msgpack/DeserializeBenchmarks.kt @@ -0,0 +1,58 @@ +package com.ensarsarajcic.kotlinx.serialization.msgpack + +import com.ensarsarajcic.kotlinx.serialization.msgpack.internal.BasicMsgPackDecoder +import com.ensarsarajcic.kotlinx.serialization.msgpack.stream.toMsgPackBuffer +import kotlinx.benchmark.Benchmark +import kotlinx.benchmark.BenchmarkMode +import kotlinx.benchmark.BenchmarkTimeUnit +import kotlinx.benchmark.Measurement +import kotlinx.benchmark.Mode +import kotlinx.benchmark.OutputTimeUnit +import kotlinx.benchmark.Scope +import kotlinx.benchmark.State +import kotlinx.serialization.Serializable +import kotlinx.serialization.modules.SerializersModule + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS) +@Measurement(iterations = 20, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) +@State(Scope.Benchmark) +open class DeserializeBenchmarks { + @Serializable + class SampleClassWithNestedClass( + var testString: String, + var testInt: Int, + var testBoolean: Boolean, + var testNested: NestedClass, + var secondNested: NestedClass? = null, + ) { + @Serializable + class NestedClass( + var testInt: Int? = null, + ) + } + + // The actual benchmark method + @Benchmark + fun benchmarkKotlinxSerializationMsgpack() { + val decoder = + BasicMsgPackDecoder( + MsgPackConfiguration.default.copy(ignoreUnknownKeys = true), + SerializersModule { + }, + @Suppress("ktlint:standard:max-line-length") + "85aa74657374537472696e67a3646566a774657374496e747bab74657374426f6f6c65616ec3aa746573744e657374656483aa74657374537472696e67a3646566ab74657374426f6f6c65616ec3ae616e6f74686572556e6b6e6f776ea474657374ac7365636f6e644e657374656481ab74657374426f6f6c65616ec2".hexStringToByteArray().toMsgPackBuffer(), + ) + SampleClassWithNestedClass.serializer().deserialize(decoder) + } + + @Benchmark + fun benchmarkMsgpackJs() { + @Suppress("ktlint:standard:max-line-length") + decode( + "85aa74657374537472696e67a3646566a774657374496e747bab74657374426f6f6c65616ec3aa746573744e657374656483aa74657374537472696e67a3646566ab74657374426f6f6c65616ec3ae616e6f74686572556e6b6e6f776ea474657374ac7365636f6e644e657374656481ab74657374426f6f6c65616ec2".hexStringToByteArray(), + ) + } +} + +fun String.hexStringToByteArray() = ByteArray(this.length / 2) { this.substring(it * 2, it * 2 + 2).toInt(16).toByte() } diff --git a/serialization-msgpack/src/jsBenchmark/kotlin/com.ensarsarajcic.kotlinx.serialization.msgpack/MsgpackJavascript.kt b/serialization-msgpack/src/jsBenchmark/kotlin/com.ensarsarajcic.kotlinx.serialization.msgpack/MsgpackJavascript.kt new file mode 100644 index 0000000..2ddab37 --- /dev/null +++ b/serialization-msgpack/src/jsBenchmark/kotlin/com.ensarsarajcic.kotlinx.serialization.msgpack/MsgpackJavascript.kt @@ -0,0 +1,8 @@ +@file:JsModule("@msgpack/msgpack") +@file:JsNonModule + +package com.ensarsarajcic.kotlinx.serialization.msgpack + +external fun encode(obj: Any): ByteArray + +external fun decode(bytes: ByteArray): T diff --git a/serialization-msgpack/src/jsBenchmark/kotlin/com.ensarsarajcic.kotlinx.serialization.msgpack/SerializeBenchmarks.kt b/serialization-msgpack/src/jsBenchmark/kotlin/com.ensarsarajcic.kotlinx.serialization.msgpack/SerializeBenchmarks.kt new file mode 100644 index 0000000..b1b3019 --- /dev/null +++ b/serialization-msgpack/src/jsBenchmark/kotlin/com.ensarsarajcic.kotlinx.serialization.msgpack/SerializeBenchmarks.kt @@ -0,0 +1,94 @@ +package com.ensarsarajcic.kotlinx.serialization.msgpack + +import kotlinx.benchmark.Benchmark +import kotlinx.benchmark.BenchmarkMode +import kotlinx.benchmark.BenchmarkTimeUnit +import kotlinx.benchmark.Measurement +import kotlinx.benchmark.Mode +import kotlinx.benchmark.OutputTimeUnit +import kotlinx.benchmark.Scope +import kotlinx.benchmark.State +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToByteArray + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS) +@Measurement(iterations = 20, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS) +@State(Scope.Benchmark) +open class SerializeBenchmarks { + @Serializable + data class SampleClass( + val testString: String, + val testInt: Int, + val testBoolean: Boolean, + ) + + @Serializable + data class SampleClassWithNestedClass( + val testString: String, + val testInt: Int, + val testBoolean: Boolean, + val testNested: NestedClass, + val testSample: SampleClass, + val testSampleList: List, + val testSampleMap: Map, + val extraBytes: ByteArray, + val secondNested: NestedClass? = null, + ) { + @Serializable + data class NestedClass( + val testInt: Int? = null, + ) + } + + // The actual benchmark method + @Benchmark + fun benchmarkKotlinxSerializationMsgpack() { + val instance = + SampleClassWithNestedClass( + testString = "testString", + testInt = 10, + testBoolean = true, + testNested = SampleClassWithNestedClass.NestedClass(5), + testSample = SampleClass(testString = "testString2", testInt = 17, testBoolean = false), + testSampleList = + listOf( + SampleClass("testString3", testInt = 25, testBoolean = true), + SampleClass("testString4", testInt = 100, testBoolean = false), + ), + testSampleMap = + mapOf( + "testString5" to SampleClass("testString5", testInt = 12, testBoolean = false), + "testString6" to SampleClass("testString6", testInt = 15, testBoolean = true), + ), + extraBytes = byteArrayOf(0x12, 0x13, 0x14, 0x15), + secondNested = SampleClassWithNestedClass.NestedClass(null), + ) + MsgPack.encodeToByteArray(instance) + } + + @Benchmark + fun benchmarkMsgpackJs() { + val instance = + SampleClassWithNestedClass( + testString = "testString", + testInt = 10, + testBoolean = true, + testNested = SampleClassWithNestedClass.NestedClass(5), + testSample = SampleClass(testString = "testString2", testInt = 17, testBoolean = false), + testSampleList = + listOf( + SampleClass("testString3", testInt = 25, testBoolean = true), + SampleClass("testString4", testInt = 100, testBoolean = false), + ), + testSampleMap = + mapOf( + "testString5" to SampleClass("testString5", testInt = 12, testBoolean = false), + "testString6" to SampleClass("testString6", testInt = 15, testBoolean = true), + ), + extraBytes = byteArrayOf(0x12, 0x13, 0x14, 0x15), + secondNested = SampleClassWithNestedClass.NestedClass(null), + ) + encode(instance) + } +}