mirror of
https://github.com/google/flatbuffers.git
synced 2026-07-03 21:34:12 +00:00
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 6e214c3a49 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.
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import groovy.xml.XmlParser
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform")
|
kotlin("multiplatform")
|
||||||
id("org.jetbrains.kotlinx.benchmark")
|
id("org.jetbrains.kotlinx.benchmark")
|
||||||
@@ -8,6 +10,18 @@ plugins {
|
|||||||
group = "com.google.flatbuffers.jmh"
|
group = "com.google.flatbuffers.jmh"
|
||||||
version = "2.0.0-SNAPSHOT"
|
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
|
// This plugin generates a static html page with the aggregation
|
||||||
// of all benchmarks ran. very useful visualization tool.
|
// of all benchmarks ran. very useful visualization tool.
|
||||||
jmhReport {
|
jmhReport {
|
||||||
@@ -55,13 +69,13 @@ kotlin {
|
|||||||
implementation(kotlin("stdlib-common"))
|
implementation(kotlin("stdlib-common"))
|
||||||
implementation(project(":flatbuffers-kotlin"))
|
implementation(project(":flatbuffers-kotlin"))
|
||||||
implementation(libs.kotlinx.benchmark.runtime)
|
implementation(libs.kotlinx.benchmark.runtime)
|
||||||
|
implementation("com.google.flatbuffers:flatbuffers-java:${readJavaFlatBufferVersion()}")
|
||||||
// json serializers
|
// json serializers
|
||||||
implementation(libs.moshi.kotlin)
|
implementation(libs.moshi.kotlin)
|
||||||
implementation(libs.gson)
|
implementation(libs.gson)
|
||||||
}
|
}
|
||||||
kotlin.srcDir("src/jvmMain/generated/kotlin/")
|
kotlin.srcDir("src/jvmMain/generated/kotlin/")
|
||||||
kotlin.srcDir("src/jvmMain/generated/java/")
|
kotlin.srcDir("src/jvmMain/generated/java/")
|
||||||
kotlin.srcDir("../../java/src/main/java")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ package com.google.flatbuffers.kotlin.benchmark
|
|||||||
|
|
||||||
import com.google.flatbuffers.kotlin.FlatBufferBuilder
|
import com.google.flatbuffers.kotlin.FlatBufferBuilder
|
||||||
import jmonster.JAllMonsters
|
import jmonster.JAllMonsters
|
||||||
|
import jmonster.JColor
|
||||||
import jmonster.JMonster
|
import jmonster.JMonster
|
||||||
import jmonster.JVec3
|
import jmonster.JVec3
|
||||||
|
import monster.AllMonsters
|
||||||
import monster.AllMonsters.Companion.createAllMonsters
|
import monster.AllMonsters.Companion.createAllMonsters
|
||||||
import monster.AllMonsters.Companion.createMonstersVector
|
import monster.AllMonsters.Companion.createMonstersVector
|
||||||
import monster.Monster
|
import monster.Monster
|
||||||
@@ -14,6 +16,7 @@ import monster.Monster.Companion.createInventoryVector
|
|||||||
import monster.MonsterOffsetArray
|
import monster.MonsterOffsetArray
|
||||||
import monster.Vec3
|
import monster.Vec3
|
||||||
import org.openjdk.jmh.annotations.*
|
import org.openjdk.jmh.annotations.*
|
||||||
|
import org.openjdk.jmh.infra.Blackhole
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@State(Scope.Benchmark)
|
@State(Scope.Benchmark)
|
||||||
@@ -24,45 +27,103 @@ open class FlatbufferBenchmark {
|
|||||||
|
|
||||||
val repetition = 1000000
|
val repetition = 1000000
|
||||||
val fbKotlin = FlatBufferBuilder(1024 * repetition)
|
val fbKotlin = FlatBufferBuilder(1024 * repetition)
|
||||||
|
val fbDeserializationKotlin = FlatBufferBuilder(1024 * repetition)
|
||||||
val fbJava = com.google.flatbuffers.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)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
@Benchmark
|
@Benchmark
|
||||||
fun monstersKotlin() {
|
fun monstersDeserializationKotlin(hole: Blackhole) {
|
||||||
fbKotlin.clear()
|
val monstersRef = AllMonsters.asRoot(fbDeserializationKotlin.dataBuffer())
|
||||||
val monsterName = fbKotlin.createString("MonsterName");
|
|
||||||
val items = ubyteArrayOf(0u, 1u, 2u, 3u, 4u)
|
for (i in 0 until monstersRef.monstersLength) {
|
||||||
val inv = createInventoryVector(fbKotlin, items)
|
val monster = monstersRef.monsters(i)!!
|
||||||
val monsterOffsets: MonsterOffsetArray = MonsterOffsetArray(repetition) {
|
val pos = monster.pos!!
|
||||||
Monster.startMonster(fbKotlin)
|
hole.consume(monster.name)
|
||||||
Monster.addName(fbKotlin, monsterName)
|
hole.consume(pos.x)
|
||||||
Monster.addPos(fbKotlin, Vec3.createVec3(fbKotlin, 1.0f, 2.0f, 3.0f))
|
hole.consume(pos.y)
|
||||||
Monster.addHp(fbKotlin, 80)
|
hole.consume(pos.z)
|
||||||
Monster.addMana(fbKotlin, 150)
|
hole.consume(monster.hp)
|
||||||
Monster.addInventory(fbKotlin, inv)
|
hole.consume(monster.mana)
|
||||||
Monster.endMonster(fbKotlin)
|
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)
|
@Benchmark
|
||||||
fbKotlin.finish(allMonsters)
|
fun monstersSerializationJava() {
|
||||||
|
populateMosterJava(fbJava)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
fun monstersjava() {
|
fun monstersDeserializationJava(hole: Blackhole) {
|
||||||
fbJava.clear()
|
val monstersRef = JAllMonsters.getRootAsJAllMonsters(fbDeserializationJava.dataBuffer())
|
||||||
val monsterName = fbJava.createString("MonsterName");
|
|
||||||
val inv = JMonster.createInventoryVector(fbJava, byteArrayOf(0, 1, 2, 3, 4).asUByteArray())
|
for (i in 0 until monstersRef.monstersLength) {
|
||||||
val monsters = JAllMonsters.createMonstersVector(fbJava, IntArray(repetition) {
|
val monster = monstersRef.monsters(i)!!
|
||||||
JMonster.startJMonster(fbJava)
|
val pos = monster.pos!!
|
||||||
JMonster.addName(fbJava, monsterName)
|
hole.consume(monster.name)
|
||||||
JMonster.addPos(fbJava, JVec3.createJVec3(fbJava, 1.0f, 2.0f, 3.0f))
|
hole.consume(pos.x)
|
||||||
JMonster.addHp(fbJava, 80)
|
hole.consume(pos.y)
|
||||||
JMonster.addMana(fbJava, 150)
|
hole.consume(pos.z)
|
||||||
JMonster.addInventory(fbJava, inv)
|
hole.consume(monster.hp)
|
||||||
JMonster.endJMonster(fbJava)
|
hole.consume(monster.mana)
|
||||||
})
|
hole.consume(monster.color)
|
||||||
val allMonsters = JAllMonsters.createJAllMonsters(fbJava, monsters)
|
hole.consume(monster.inventory(0))
|
||||||
fbJava.finish(allMonsters)
|
hole.consume(monster.inventory(1))
|
||||||
|
hole.consume(monster.inventory(2))
|
||||||
|
hole.consume(monster.inventory(3))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ public open class Table {
|
|||||||
/** Used to hold the vtable size. */
|
/** Used to hold the vtable size. */
|
||||||
public var vtableSize: Int = 0
|
public var vtableSize: Int = 0
|
||||||
|
|
||||||
protected inline fun <reified T> Int.invalid(default: T, valid: (Int) -> T) : T =
|
protected inline fun <reified T> Int.invalid(default: T, crossinline valid: (Int) -> T) : T =
|
||||||
if (this != 0) valid(this) else default
|
if (this != 0) valid(this) else default
|
||||||
|
|
||||||
protected inline fun <reified T> lookupField(i: Int, default: T, found: (Int) -> T) : T =
|
protected inline fun <reified T> lookupField(i: Int, default: T, crossinline found: (Int) -> T) : T =
|
||||||
offset(i).invalid(default) { found(it) }
|
offset(i).invalid(default) { found(it) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -614,10 +614,6 @@ class KotlinKMPGenerator : public BaseGenerator {
|
|||||||
// accessor object. This is to allow object reuse.
|
// accessor object. This is to allow object reuse.
|
||||||
GenerateFunOneLine(writer, "init", "i: Int, buffer: ReadWriteBuffer",
|
GenerateFunOneLine(writer, "init", "i: Int, buffer: ReadWriteBuffer",
|
||||||
esc_type, [&]() { writer += "reset(i, buffer)"; });
|
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
|
writer += ""; // line break
|
||||||
|
|
||||||
// Generate all getters
|
// Generate all getters
|
||||||
@@ -740,7 +736,7 @@ class KotlinKMPGenerator : public BaseGenerator {
|
|||||||
writer += "}"; // end comp < 0
|
writer += "}"; // end comp < 0
|
||||||
writer += "else -> {";
|
writer += "else -> {";
|
||||||
writer.IncrementIdentLevel();
|
writer.IncrementIdentLevel();
|
||||||
writer += "return (obj ?: {{struct_name}}()).assign(tableOffset, bb)";
|
writer += "return (obj ?: {{struct_name}}()).init(tableOffset, bb)";
|
||||||
writer.DecrementIdentLevel();
|
writer.DecrementIdentLevel();
|
||||||
writer += "}"; // end else
|
writer += "}"; // end else
|
||||||
writer.DecrementIdentLevel();
|
writer.DecrementIdentLevel();
|
||||||
@@ -1068,11 +1064,11 @@ class KotlinKMPGenerator : public BaseGenerator {
|
|||||||
if (struct_def.fixed) {
|
if (struct_def.fixed) {
|
||||||
// create getter with object reuse
|
// create getter with object reuse
|
||||||
// ex:
|
// ex:
|
||||||
// fun pos(obj: Vec3) : Vec3? = obj.assign(bufferPos + 4, bb)
|
// fun pos(obj: Vec3) : Vec3? = obj.init(bufferPos + 4, bb)
|
||||||
// ? adds nullability annotation
|
// ? adds nullability annotation
|
||||||
GenerateFunOneLine(
|
GenerateFunOneLine(
|
||||||
writer, field_name, "obj: " + field_type, return_type, [&]() {
|
writer, field_name, "obj: " + field_type, return_type, [&]() {
|
||||||
writer += "obj.assign(bufferPos + {{offset}}, bb)";
|
writer += "obj.init(bufferPos + {{offset}}, bb)";
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// create getter with object reuse
|
// create getter with object reuse
|
||||||
@@ -1080,7 +1076,7 @@ class KotlinKMPGenerator : public BaseGenerator {
|
|||||||
// fun pos(obj: Vec3) : Vec3? {
|
// fun pos(obj: Vec3) : Vec3? {
|
||||||
// val o = offset(4)
|
// val o = offset(4)
|
||||||
// return if(o != 0) {
|
// return if(o != 0) {
|
||||||
// obj.assign(o + bufferPos, bb)
|
// obj.init(o + bufferPos, bb)
|
||||||
// else {
|
// else {
|
||||||
// null
|
// null
|
||||||
// }
|
// }
|
||||||
@@ -1092,7 +1088,7 @@ class KotlinKMPGenerator : public BaseGenerator {
|
|||||||
|
|
||||||
writer.SetValue("seek", Indirect("it + bufferPos", fixed));
|
writer.SetValue("seek", Indirect("it + bufferPos", fixed));
|
||||||
writer += LookupFieldOneLine(
|
writer += LookupFieldOneLine(
|
||||||
offset_val, "obj.assign({{seek}}, bb)", "null");
|
offset_val, "obj.init({{seek}}, bb)", "null");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1142,7 +1138,7 @@ class KotlinKMPGenerator : public BaseGenerator {
|
|||||||
case BASE_TYPE_STRUCT: {
|
case BASE_TYPE_STRUCT: {
|
||||||
bool fixed = vectortype.struct_def->fixed;
|
bool fixed = vectortype.struct_def->fixed;
|
||||||
writer.SetValue("index", Indirect(index, fixed));
|
writer.SetValue("index", Indirect(index, fixed));
|
||||||
found = "obj.assign({{index}}, bb)";
|
found = "obj.init({{index}}, bb)";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BASE_TYPE_UNION:
|
case BASE_TYPE_UNION:
|
||||||
@@ -1247,7 +1243,7 @@ class KotlinKMPGenerator : public BaseGenerator {
|
|||||||
writer, nested_method_name, "obj: " + nested_type_name,
|
writer, nested_method_name, "obj: " + nested_type_name,
|
||||||
nested_type_name + "?", [&]() {
|
nested_type_name + "?", [&]() {
|
||||||
writer += LookupFieldOneLine(
|
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}}",
|
writer, "asRoot", "buffer: ReadWriteBuffer, obj: {{gr_name}}",
|
||||||
struct_name, [&]() {
|
struct_name, [&]() {
|
||||||
writer +=
|
writer +=
|
||||||
"obj.assign(buffer.getInt(buffer.limit) + buffer.limit, buffer)";
|
"obj.init(buffer.getInt(buffer.limit) + buffer.limit, buffer)";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user