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)"; }); }