From 85088a196d5c0096460f821c3f189fd9f7b471d2 Mon Sep 17 00:00:00 2001 From: Paulo Pinheiro Date: Wed, 31 May 2023 20:02:39 +0200 Subject: [PATCH] Small optimization on "deserialization" and fix on benchmarks again (#7982) * [Kotlin] Small optimizations and benchmark on deserialization * [Kotlin] Remove redudant assign() method (use init() instead) * [Kotlin] Fix benchmark run after change in flatbuffers-java deps Commit 6e214c3a498fe7f9c7923aecdae4c789e224ad18 fixes Kotlin build, but makes the kotlin-benchmark plugin misses the java classes at runtime, causing NotClassFoundError. The alternative to solve the issue is to read java's pom.xml to get the latest java version and use it as dependency. With that we avoid compilation errors on a new version and keep benchmark plugin happy. --- kotlin/benchmark/build.gradle.kts | 16 ++- .../kotlin/benchmark/FlatbufferBenchmark.kt | 123 +++++++++++++----- .../google/flatbuffers/kotlin/Flatbuffers.kt | 4 +- src/idl_gen_kotlin_kmp.cpp | 20 ++- 4 files changed, 117 insertions(+), 46 deletions(-) diff --git a/kotlin/benchmark/build.gradle.kts b/kotlin/benchmark/build.gradle.kts index 21617da08..1e801660d 100644 --- a/kotlin/benchmark/build.gradle.kts +++ b/kotlin/benchmark/build.gradle.kts @@ -1,3 +1,5 @@ +import groovy.xml.XmlParser + plugins { kotlin("multiplatform") id("org.jetbrains.kotlinx.benchmark") @@ -8,6 +10,18 @@ plugins { group = "com.google.flatbuffers.jmh" version = "2.0.0-SNAPSHOT" +// Reads latest version from Java's runtime pom.xml, +// so we can use it for benchmarking against Kotlin's +// runtime +fun readJavaFlatBufferVersion(): String { + val pom = XmlParser().parse(File("../java/pom.xml")) + val versionTag = pom.children().find { + val node = it as groovy.util.Node + node.name().toString().contains("version") + } as groovy.util.Node + return versionTag.value().toString() +} + // This plugin generates a static html page with the aggregation // of all benchmarks ran. very useful visualization tool. jmhReport { @@ -55,13 +69,13 @@ kotlin { implementation(kotlin("stdlib-common")) implementation(project(":flatbuffers-kotlin")) implementation(libs.kotlinx.benchmark.runtime) + implementation("com.google.flatbuffers:flatbuffers-java:${readJavaFlatBufferVersion()}") // json serializers implementation(libs.moshi.kotlin) implementation(libs.gson) } kotlin.srcDir("src/jvmMain/generated/kotlin/") kotlin.srcDir("src/jvmMain/generated/java/") - kotlin.srcDir("../../java/src/main/java") } } } diff --git a/kotlin/benchmark/src/jvmMain/kotlin/com/google/flatbuffers/kotlin/benchmark/FlatbufferBenchmark.kt b/kotlin/benchmark/src/jvmMain/kotlin/com/google/flatbuffers/kotlin/benchmark/FlatbufferBenchmark.kt index 5c37b95f1..a4f3d250d 100644 --- a/kotlin/benchmark/src/jvmMain/kotlin/com/google/flatbuffers/kotlin/benchmark/FlatbufferBenchmark.kt +++ b/kotlin/benchmark/src/jvmMain/kotlin/com/google/flatbuffers/kotlin/benchmark/FlatbufferBenchmark.kt @@ -5,8 +5,10 @@ package com.google.flatbuffers.kotlin.benchmark import com.google.flatbuffers.kotlin.FlatBufferBuilder import jmonster.JAllMonsters +import jmonster.JColor import jmonster.JMonster import jmonster.JVec3 +import monster.AllMonsters import monster.AllMonsters.Companion.createAllMonsters import monster.AllMonsters.Companion.createMonstersVector import monster.Monster @@ -14,6 +16,7 @@ import monster.Monster.Companion.createInventoryVector import monster.MonsterOffsetArray import monster.Vec3 import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.infra.Blackhole import java.util.concurrent.TimeUnit @State(Scope.Benchmark) @@ -24,45 +27,103 @@ open class FlatbufferBenchmark { val repetition = 1000000 val fbKotlin = FlatBufferBuilder(1024 * repetition) + val fbDeserializationKotlin = FlatBufferBuilder(1024 * repetition) val fbJava = com.google.flatbuffers.FlatBufferBuilder(1024 * repetition) + val fbDeserializationJava = com.google.flatbuffers.FlatBufferBuilder(1024 * repetition) + + init { + populateMosterKotlin(fbDeserializationKotlin) + populateMosterJava(fbDeserializationJava) + } + @OptIn(ExperimentalUnsignedTypes::class) + private fun populateMosterKotlin(fb: FlatBufferBuilder) { + fb.clear() + val monsterName = fb.createString("MonsterName"); + val items = ubyteArrayOf(0u, 1u, 2u, 3u, 4u) + val inv = createInventoryVector(fb, items) + val monsterOffsets: MonsterOffsetArray = MonsterOffsetArray(repetition) { + Monster.startMonster(fb) + Monster.addName(fb, monsterName) + Monster.addPos(fb, Vec3.createVec3(fb, 1.0f, 2.0f, 3.0f)) + Monster.addHp(fb, 80) + Monster.addMana(fb, 150) + Monster.addInventory(fb, inv) + Monster.addColor(fb, monster.Color.Red) + Monster.endMonster(fb) + } + val monsters = createMonstersVector(fb, monsterOffsets) + val allMonsters = createAllMonsters(fb, monsters) + fb.finish(allMonsters) + } + + @OptIn(ExperimentalUnsignedTypes::class) + private fun populateMosterJava(fb: com.google.flatbuffers.FlatBufferBuilder){ + fb.clear() + val monsterName = fb.createString("MonsterName"); + val inv = JMonster.createInventoryVector(fb, ubyteArrayOf(0u, 1u, 2u, 3u, 4u)) + val monsters = JAllMonsters.createMonstersVector(fb, IntArray(repetition) { + JMonster.startJMonster(fb) + JMonster.addName(fb, monsterName) + JMonster.addPos(fb, JVec3.createJVec3(fb, 1.0f, 2.0f, 3.0f)) + JMonster.addHp(fb, 80) + JMonster.addMana(fb, 150) + JMonster.addInventory(fb, inv) + JMonster.addColor(fb, JColor.Red) + JMonster.endJMonster(fb) + }) + val allMonsters = JAllMonsters.createJAllMonsters(fb, monsters) + fb.finish(allMonsters) + } + @Benchmark + fun monstersSerializationKotlin() { + populateMosterKotlin(fbKotlin) + } @OptIn(ExperimentalUnsignedTypes::class) @Benchmark - fun monstersKotlin() { - fbKotlin.clear() - val monsterName = fbKotlin.createString("MonsterName"); - val items = ubyteArrayOf(0u, 1u, 2u, 3u, 4u) - val inv = createInventoryVector(fbKotlin, items) - val monsterOffsets: MonsterOffsetArray = MonsterOffsetArray(repetition) { - Monster.startMonster(fbKotlin) - Monster.addName(fbKotlin, monsterName) - Monster.addPos(fbKotlin, Vec3.createVec3(fbKotlin, 1.0f, 2.0f, 3.0f)) - Monster.addHp(fbKotlin, 80) - Monster.addMana(fbKotlin, 150) - Monster.addInventory(fbKotlin, inv) - Monster.endMonster(fbKotlin) + fun monstersDeserializationKotlin(hole: Blackhole) { + val monstersRef = AllMonsters.asRoot(fbDeserializationKotlin.dataBuffer()) + + for (i in 0 until monstersRef.monstersLength) { + val monster = monstersRef.monsters(i)!! + val pos = monster.pos!! + hole.consume(monster.name) + hole.consume(pos.x) + hole.consume(pos.y) + hole.consume(pos.z) + hole.consume(monster.hp) + hole.consume(monster.mana) + hole.consume(monster.color) + hole.consume(monster.inventory(0).toByte()) + hole.consume(monster.inventory(1)) + hole.consume(monster.inventory(2)) + hole.consume(monster.inventory(3)) } - val monsters = createMonstersVector(fbKotlin, monsterOffsets) - val allMonsters = createAllMonsters(fbKotlin, monsters) - fbKotlin.finish(allMonsters) + } + @Benchmark + fun monstersSerializationJava() { + populateMosterJava(fbJava) } @Benchmark - fun monstersjava() { - fbJava.clear() - val monsterName = fbJava.createString("MonsterName"); - val inv = JMonster.createInventoryVector(fbJava, byteArrayOf(0, 1, 2, 3, 4).asUByteArray()) - val monsters = JAllMonsters.createMonstersVector(fbJava, IntArray(repetition) { - JMonster.startJMonster(fbJava) - JMonster.addName(fbJava, monsterName) - JMonster.addPos(fbJava, JVec3.createJVec3(fbJava, 1.0f, 2.0f, 3.0f)) - JMonster.addHp(fbJava, 80) - JMonster.addMana(fbJava, 150) - JMonster.addInventory(fbJava, inv) - JMonster.endJMonster(fbJava) - }) - val allMonsters = JAllMonsters.createJAllMonsters(fbJava, monsters) - fbJava.finish(allMonsters) + fun monstersDeserializationJava(hole: Blackhole) { + val monstersRef = JAllMonsters.getRootAsJAllMonsters(fbDeserializationJava.dataBuffer()) + + for (i in 0 until monstersRef.monstersLength) { + val monster = monstersRef.monsters(i)!! + val pos = monster.pos!! + hole.consume(monster.name) + hole.consume(pos.x) + hole.consume(pos.y) + hole.consume(pos.z) + hole.consume(monster.hp) + hole.consume(monster.mana) + hole.consume(monster.color) + hole.consume(monster.inventory(0)) + hole.consume(monster.inventory(1)) + hole.consume(monster.inventory(2)) + hole.consume(monster.inventory(3)) + } } } diff --git a/kotlin/flatbuffers-kotlin/src/commonMain/kotlin/com/google/flatbuffers/kotlin/Flatbuffers.kt b/kotlin/flatbuffers-kotlin/src/commonMain/kotlin/com/google/flatbuffers/kotlin/Flatbuffers.kt index bbebd29de..cdfe09a67 100644 --- a/kotlin/flatbuffers-kotlin/src/commonMain/kotlin/com/google/flatbuffers/kotlin/Flatbuffers.kt +++ b/kotlin/flatbuffers-kotlin/src/commonMain/kotlin/com/google/flatbuffers/kotlin/Flatbuffers.kt @@ -81,10 +81,10 @@ public open class Table { /** Used to hold the vtable size. */ public var vtableSize: Int = 0 - protected inline fun Int.invalid(default: T, valid: (Int) -> T) : T = + protected inline fun Int.invalid(default: T, crossinline valid: (Int) -> T) : T = if (this != 0) valid(this) else default - protected inline fun lookupField(i: Int, default: T, found: (Int) -> T) : T = + protected inline fun lookupField(i: Int, default: T, crossinline found: (Int) -> T) : T = offset(i).invalid(default) { found(it) } /** diff --git a/src/idl_gen_kotlin_kmp.cpp b/src/idl_gen_kotlin_kmp.cpp index cda77d94d..2dba49425 100644 --- a/src/idl_gen_kotlin_kmp.cpp +++ b/src/idl_gen_kotlin_kmp.cpp @@ -614,10 +614,6 @@ class KotlinKMPGenerator : public BaseGenerator { // accessor object. This is to allow object reuse. GenerateFunOneLine(writer, "init", "i: Int, buffer: ReadWriteBuffer", esc_type, [&]() { writer += "reset(i, buffer)"; }); - - // Generate assign method - GenerateFunOneLine(writer, "assign", "i: Int, buffer: ReadWriteBuffer", - esc_type, [&]() { writer += "init(i, buffer)"; }); writer += ""; // line break // Generate all getters @@ -740,7 +736,7 @@ class KotlinKMPGenerator : public BaseGenerator { writer += "}"; // end comp < 0 writer += "else -> {"; writer.IncrementIdentLevel(); - writer += "return (obj ?: {{struct_name}}()).assign(tableOffset, bb)"; + writer += "return (obj ?: {{struct_name}}()).init(tableOffset, bb)"; writer.DecrementIdentLevel(); writer += "}"; // end else writer.DecrementIdentLevel(); @@ -1068,11 +1064,11 @@ class KotlinKMPGenerator : public BaseGenerator { if (struct_def.fixed) { // create getter with object reuse // ex: - // fun pos(obj: Vec3) : Vec3? = obj.assign(bufferPos + 4, bb) + // fun pos(obj: Vec3) : Vec3? = obj.init(bufferPos + 4, bb) // ? adds nullability annotation GenerateFunOneLine( writer, field_name, "obj: " + field_type, return_type, [&]() { - writer += "obj.assign(bufferPos + {{offset}}, bb)"; + writer += "obj.init(bufferPos + {{offset}}, bb)"; }); } else { // create getter with object reuse @@ -1080,7 +1076,7 @@ class KotlinKMPGenerator : public BaseGenerator { // fun pos(obj: Vec3) : Vec3? { // val o = offset(4) // return if(o != 0) { - // obj.assign(o + bufferPos, bb) + // obj.init(o + bufferPos, bb) // else { // null // } @@ -1092,7 +1088,7 @@ class KotlinKMPGenerator : public BaseGenerator { writer.SetValue("seek", Indirect("it + bufferPos", fixed)); writer += LookupFieldOneLine( - offset_val, "obj.assign({{seek}}, bb)", "null"); + offset_val, "obj.init({{seek}}, bb)", "null"); }); } break; @@ -1142,7 +1138,7 @@ class KotlinKMPGenerator : public BaseGenerator { case BASE_TYPE_STRUCT: { bool fixed = vectortype.struct_def->fixed; writer.SetValue("index", Indirect(index, fixed)); - found = "obj.assign({{index}}, bb)"; + found = "obj.init({{index}}, bb)"; break; } case BASE_TYPE_UNION: @@ -1247,7 +1243,7 @@ class KotlinKMPGenerator : public BaseGenerator { writer, nested_method_name, "obj: " + nested_type_name, nested_type_name + "?", [&]() { writer += LookupFieldOneLine( - offset_val, "obj.assign(indirect(vector(it)), bb)", "null"); + offset_val, "obj.init(indirect(vector(it)), bb)", "null"); }); } @@ -1348,7 +1344,7 @@ class KotlinKMPGenerator : public BaseGenerator { writer, "asRoot", "buffer: ReadWriteBuffer, obj: {{gr_name}}", struct_name, [&]() { writer += - "obj.assign(buffer.getInt(buffer.limit) + buffer.limit, buffer)"; + "obj.init(buffer.getInt(buffer.limit) + buffer.limit, buffer)"; }); }