mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-26 18:32:41 +00:00
This commit contains the initial implementation of Flexbuffers in Kotlin. The code was ported based (#6387)
on the current Java Implementation. The code dependencies related to JVM were removed and the project is able to target all available platforms. The only requirement to implement to fully support a target is to implement functions described in `ByteArray.kt`. Right now the code support JVM and native targets. JS port still missing, but just be trivial to introduce. Currently, only the `jvm` and `macosX64` targets are enabled until we figure out how to enable tests on all platforms on CI. A submodule called "benchmark" is also introduced. It contains a series of benchmarks comparing Java and Kotlin implementations of FlexBuffers and the UTF8 API. Finally, this commit does not contain the scripts necessary to publish the artifacts. This will be introduced at a later stage once the team has an agreement on how to rollout Kotlin releases.
This commit is contained in:
1
kotlin/benchmark/src/jvmMain/java
Symbolic link
1
kotlin/benchmark/src/jvmMain/java
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../java/
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2021 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.flatbuffers.kotlin.benchmark
|
||||
|
||||
import com.google.flatbuffers.ArrayReadWriteBuf
|
||||
import com.google.flatbuffers.FlexBuffers
|
||||
import com.google.flatbuffers.FlexBuffersBuilder.BUILDER_FLAG_SHARE_ALL
|
||||
import com.google.flatbuffers.kotlin.FlexBuffersBuilder
|
||||
import com.google.flatbuffers.kotlin.getRoot
|
||||
import kotlinx.benchmark.Blackhole
|
||||
import org.openjdk.jmh.annotations.Benchmark
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode
|
||||
import org.openjdk.jmh.annotations.Measurement
|
||||
import org.openjdk.jmh.annotations.Mode
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit
|
||||
import org.openjdk.jmh.annotations.Scope
|
||||
import org.openjdk.jmh.annotations.Setup
|
||||
import org.openjdk.jmh.annotations.State
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@Measurement(iterations = 20, time = 1, timeUnit = TimeUnit.NANOSECONDS)
|
||||
class KotlinBenchmark {
|
||||
|
||||
var initialCapacity = 1024
|
||||
var value: Double = 0.0
|
||||
val stringKey = Array(500) { "Ḧ̵̘́ȩ̵̐myFairlyBigKey$it" }
|
||||
val stringValue = Array(500) { "Ḧ̵̘́ȩ̵̐myFairlyBigValue$it" }
|
||||
val bigIntArray = IntArray(5000) { it }
|
||||
|
||||
@Setup
|
||||
fun setUp() {
|
||||
value = 3.0
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun mapKotlin(blackhole: Blackhole) {
|
||||
val kBuilder = FlexBuffersBuilder(initialCapacity, FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
|
||||
kBuilder.putMap {
|
||||
this["hello"] = "world"
|
||||
this["int"] = 10
|
||||
this["float"] = 12.3
|
||||
this["intarray"] = bigIntArray
|
||||
this.putMap("myMap") {
|
||||
this["cool"] = "beans"
|
||||
}
|
||||
}
|
||||
val ref = getRoot(kBuilder.finish())
|
||||
val map = ref.toMap()
|
||||
blackhole.consume(map.size)
|
||||
blackhole.consume(map["hello"].toString())
|
||||
blackhole.consume(map["int"].toInt())
|
||||
blackhole.consume(map["float"].toDouble())
|
||||
blackhole.consume(map["intarray"].toIntArray())
|
||||
blackhole.consume(ref["myMap"]["cool"].toString())
|
||||
blackhole.consume(ref["invalid_key"].isNull)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun mapJava(blackhole: Blackhole) {
|
||||
val jBuilder = com.google.flatbuffers.FlexBuffersBuilder(ArrayReadWriteBuf(initialCapacity), BUILDER_FLAG_SHARE_ALL)
|
||||
val startMap = jBuilder.startMap()
|
||||
jBuilder.putString("hello", "world")
|
||||
jBuilder.putInt("int", 10)
|
||||
jBuilder.putFloat("float", 12.3)
|
||||
|
||||
val startVec = jBuilder.startVector()
|
||||
bigIntArray.forEach { jBuilder.putInt(it) }
|
||||
jBuilder.endVector("intarray", startVec, true, false)
|
||||
|
||||
val startInnerMap = jBuilder.startMap()
|
||||
jBuilder.putString("cool", "beans")
|
||||
jBuilder.endMap("myMap", startInnerMap)
|
||||
|
||||
jBuilder.endMap(null, startMap)
|
||||
val ref = FlexBuffers.getRoot(jBuilder.finish())
|
||||
val map = ref.asMap()
|
||||
blackhole.consume(map.size())
|
||||
blackhole.consume(map.get("hello").toString())
|
||||
blackhole.consume(map.get("int").asInt())
|
||||
blackhole.consume(map.get("float").asFloat())
|
||||
val vec = map.get("intarray").asVector()
|
||||
blackhole.consume(IntArray(vec.size()) { vec.get(it).asInt() })
|
||||
|
||||
blackhole.consume(ref.asMap()["myMap"].asMap()["cool"].toString())
|
||||
blackhole.consume(ref.asMap()["invalid_key"].isNull)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun intArrayKotlin(blackhole: Blackhole) {
|
||||
val kBuilder = FlexBuffersBuilder(initialCapacity, FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
|
||||
kBuilder.put(bigIntArray)
|
||||
val root = getRoot(kBuilder.finish())
|
||||
blackhole.consume(root.toIntArray())
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun intArrayJava(blackhole: Blackhole) {
|
||||
val jBuilder = com.google.flatbuffers.FlexBuffersBuilder(ArrayReadWriteBuf(initialCapacity), BUILDER_FLAG_SHARE_ALL)
|
||||
val v = jBuilder.startVector()
|
||||
bigIntArray.forEach { jBuilder.putInt(it) }
|
||||
jBuilder.endVector(null, v, true, false)
|
||||
jBuilder.finish()
|
||||
val root = FlexBuffers.getRoot(jBuilder.buffer)
|
||||
val vec = root.asVector()
|
||||
blackhole.consume(
|
||||
IntArray(vec.size()) {
|
||||
vec[it].asInt()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun stringArrayKotlin(blackhole: Blackhole) {
|
||||
val kBuilder = FlexBuffersBuilder(initialCapacity, FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
|
||||
kBuilder.putVector { stringValue.forEach { kBuilder.put(it) } }
|
||||
kBuilder.finish()
|
||||
val root = getRoot(kBuilder.buffer)
|
||||
val vec = root.toVector()
|
||||
blackhole.consume(Array(vec.size) { vec[it].toString() })
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun stringArrayJava(blackhole: Blackhole) {
|
||||
val jBuilder = com.google.flatbuffers.FlexBuffersBuilder(ArrayReadWriteBuf(initialCapacity), BUILDER_FLAG_SHARE_ALL)
|
||||
val v = jBuilder.startVector()
|
||||
stringValue.forEach { jBuilder.putString(it) }
|
||||
jBuilder.endVector(null, v, false, false)
|
||||
jBuilder.finish()
|
||||
val root = FlexBuffers.getRoot(jBuilder.buffer)
|
||||
val vec = root.asVector()
|
||||
blackhole.consume(Array(vec.size()) { vec[it].toString() })
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun stringMapKotlin(blackhole: Blackhole) {
|
||||
val kBuilder = FlexBuffersBuilder(initialCapacity, FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
|
||||
val pos = kBuilder.startMap()
|
||||
for (i in stringKey.indices) {
|
||||
kBuilder[stringKey[i]] = stringValue[i]
|
||||
}
|
||||
kBuilder.endMap(pos)
|
||||
val ref = getRoot(kBuilder.finish())
|
||||
val map = ref.toMap()
|
||||
val keys = map.keys
|
||||
|
||||
for (key in keys) {
|
||||
blackhole.consume(map[key.toString()].toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun stringMapBytIndexKotlin(blackhole: Blackhole) {
|
||||
val kBuilder = FlexBuffersBuilder(initialCapacity, FlexBuffersBuilder.SHARE_KEYS_AND_STRINGS)
|
||||
val pos = kBuilder.startMap()
|
||||
for (i in stringKey.indices) {
|
||||
kBuilder[stringKey[i]] = stringValue[i]
|
||||
}
|
||||
kBuilder.endMap(pos)
|
||||
val ref = getRoot(kBuilder.finish())
|
||||
val map = ref.toMap()
|
||||
for (index in 0 until map.size) {
|
||||
blackhole.consume(map[index].toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun stringMapJava(blackhole: Blackhole) {
|
||||
val jBuilder = com.google.flatbuffers.FlexBuffersBuilder(ArrayReadWriteBuf(initialCapacity), BUILDER_FLAG_SHARE_ALL)
|
||||
val v = jBuilder.startMap()
|
||||
for (i in stringKey.indices) {
|
||||
jBuilder.putString(stringKey[i], stringValue[i])
|
||||
}
|
||||
jBuilder.endMap(null, v)
|
||||
val ref = FlexBuffers.getRoot(jBuilder.finish())
|
||||
val map = ref.asMap()
|
||||
val keyVec = map.keys()
|
||||
for (i in 0 until keyVec.size()) {
|
||||
val s = keyVec[i].toString()
|
||||
blackhole.consume(map[s].toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 2021 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.flatbuffers.kotlin.benchmark
|
||||
|
||||
import com.google.flatbuffers.kotlin.ArrayReadWriteBuffer
|
||||
import com.google.flatbuffers.kotlin.Key
|
||||
import com.google.flatbuffers.kotlin.Utf8
|
||||
import kotlinx.benchmark.Blackhole
|
||||
import org.openjdk.jmh.annotations.Benchmark
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode
|
||||
import org.openjdk.jmh.annotations.Measurement
|
||||
import org.openjdk.jmh.annotations.Mode
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit
|
||||
import org.openjdk.jmh.annotations.Scope
|
||||
import org.openjdk.jmh.annotations.Setup
|
||||
import org.openjdk.jmh.annotations.State
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.random.Random
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||
@Measurement(iterations = 100, time = 1, timeUnit = TimeUnit.MICROSECONDS)
|
||||
class UTF8Benchmark {
|
||||
|
||||
private final val sampleSize = 5000
|
||||
private final val stringSize = 25
|
||||
final var sampleSmallUtf8 = (0..sampleSize).map { populateUTF8(stringSize) }.toList()
|
||||
final var sampleSmallUtf8Decoded = sampleSmallUtf8.map { it.encodeToByteArray() }.toList()
|
||||
final var sampleSmallAscii = (0..sampleSize).map { populateAscii(stringSize) }.toList()
|
||||
final var sampleSmallAsciiDecoded = sampleSmallAscii.map { it.encodeToByteArray() }.toList()
|
||||
|
||||
@Setup
|
||||
fun setUp() {
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun encodeUtf8KotlinStandard(blackhole: Blackhole) {
|
||||
for (i in sampleSmallUtf8) {
|
||||
blackhole.consume(i.encodeToByteArray())
|
||||
}
|
||||
}
|
||||
@Benchmark
|
||||
fun encodeUtf8KotlinFlatbuffers(blackhole: Blackhole) {
|
||||
for (i in sampleSmallUtf8) {
|
||||
val byteArray = ByteArray((i.length * 4))
|
||||
blackhole.consume(Utf8.encodeUtf8Array(i, byteArray, 0, byteArray.size))
|
||||
}
|
||||
}
|
||||
@Benchmark
|
||||
fun encodeUtf8JavaFlatbuffers(blackhole: Blackhole) {
|
||||
val javaUtf8 = com.google.flatbuffers.Utf8.getDefault()
|
||||
for (i in sampleSmallUtf8) {
|
||||
val byteBuffer = ByteBuffer.wrap(ByteArray(i.length * 4))
|
||||
blackhole.consume(javaUtf8.encodeUtf8(i, byteBuffer))
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun decodeUtf8KotlinStandard(blackhole: Blackhole) {
|
||||
for (ary in sampleSmallUtf8Decoded) {
|
||||
blackhole.consume(ary.decodeToString())
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun decodeUtf8KotlinFlatbuffers(blackhole: Blackhole) {
|
||||
for (ary in sampleSmallUtf8Decoded) {
|
||||
blackhole.consume(Utf8.decodeUtf8Array(ary, 0, ary.size))
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun decodeUtf8JavaFlatbuffers(blackhole: Blackhole) {
|
||||
val javaUtf8 = com.google.flatbuffers.Utf8.getDefault()
|
||||
for (ary in sampleSmallUtf8Decoded) {
|
||||
val byteBuffer = ByteBuffer.wrap(ary)
|
||||
blackhole.consume(javaUtf8.decodeUtf8(byteBuffer, 0, ary.size))
|
||||
}
|
||||
}
|
||||
|
||||
// ASCII TESTS
|
||||
|
||||
@Benchmark
|
||||
fun encodeAsciiKotlinStandard(blackhole: Blackhole) {
|
||||
for (i in sampleSmallAscii) {
|
||||
blackhole.consume(i.encodeToByteArray())
|
||||
}
|
||||
}
|
||||
@Benchmark
|
||||
fun encodeAsciiKotlinFlatbuffers(blackhole: Blackhole) {
|
||||
for (i in sampleSmallAscii) {
|
||||
val byteArray = ByteArray(i.length) // Utf8.encodedLength(i))
|
||||
blackhole.consume(Utf8.encodeUtf8Array(i, byteArray, 0, byteArray.size))
|
||||
}
|
||||
}
|
||||
@Benchmark
|
||||
fun encodeAsciiJavaFlatbuffers(blackhole: Blackhole) {
|
||||
val javaUtf8 = com.google.flatbuffers.Utf8.getDefault()
|
||||
for (i in sampleSmallAscii) {
|
||||
val byteBuffer = ByteBuffer.wrap(ByteArray(i.length))
|
||||
blackhole.consume(javaUtf8.encodeUtf8(i, byteBuffer))
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun decodeAsciiKotlinStandard(blackhole: Blackhole) {
|
||||
|
||||
for (ary in sampleSmallAsciiDecoded) {
|
||||
String(ary)
|
||||
blackhole.consume(ary.decodeToString())
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun decodeAsciiKotlinFlatbuffers(blackhole: Blackhole) {
|
||||
for (ary in sampleSmallAsciiDecoded) {
|
||||
blackhole.consume(Utf8.decodeUtf8Array(ary, 0, ary.size))
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun decodeAsciiJavaFlatbuffers(blackhole: Blackhole) {
|
||||
val javaUtf8 = com.google.flatbuffers.Utf8.getDefault()
|
||||
for (ary in sampleSmallAsciiDecoded) {
|
||||
val byteBuffer = ByteBuffer.wrap(ary)
|
||||
blackhole.consume(javaUtf8.decodeUtf8(byteBuffer, 0, ary.size))
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun readAllCharsString(blackhole: Blackhole) {
|
||||
for (ary in sampleSmallAsciiDecoded) {
|
||||
val key = Utf8.decodeUtf8Array(ary, 0, ary.size)
|
||||
for (i in key.indices) {
|
||||
blackhole.consume(key[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
fun readAllCharsCharSequence(blackhole: Blackhole) {
|
||||
for (ary in sampleSmallAsciiDecoded) {
|
||||
val key = Key(ArrayReadWriteBuffer(ary), 0, ary.size)
|
||||
for (i in 0 until key.sizeInChars) {
|
||||
blackhole.consume(key[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun populateAscii(size: Int): String {
|
||||
val data = ByteArray(size)
|
||||
for (i in data.indices) {
|
||||
data[i] = Random.nextInt(0, 127).toByte()
|
||||
}
|
||||
|
||||
return String(data, 0, data.size)
|
||||
}
|
||||
|
||||
// generate a string having at least length N
|
||||
// can exceed by up to 3 chars, returns the actual length
|
||||
fun populateUTF8(size: Int): String {
|
||||
val data = ByteArray(size + 3)
|
||||
var i = 0
|
||||
while (i < size) {
|
||||
val w = Random.nextInt() and 0xFF
|
||||
when {
|
||||
w < 0x80 -> data[i++] = 0x20; // w;
|
||||
w < 0xE0 -> {
|
||||
data[i++] = (0xC2 + Random.nextInt() % (0xDF - 0xC2 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
}
|
||||
w == 0xE0 -> {
|
||||
data[i++] = w.toByte()
|
||||
data[i++] = (0xA0 + Random.nextInt() % (0xBF - 0xA0 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
}
|
||||
w <= 0xEC -> {
|
||||
data[i++] = w.toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
}
|
||||
w == 0xED -> {
|
||||
data[i++] = w.toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0x9F - 0x80 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
}
|
||||
w <= 0xEF -> {
|
||||
data[i++] = w.toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
}
|
||||
w < 0xF0 -> {
|
||||
data[i++] = (0xF1 + Random.nextInt() % (0xF3 - 0xF1 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
}
|
||||
w == 0xF0 -> {
|
||||
data[i++] = w.toByte()
|
||||
data[i++] = (0x90 + Random.nextInt() % (0xBF - 0x90 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
}
|
||||
w <= 0xF3 -> {
|
||||
data[i++] = (0xF1 + Random.nextInt() % (0xF3 - 0xF1 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
}
|
||||
w == 0xF4 -> {
|
||||
data[i++] = w.toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0x8F - 0x80 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
data[i++] = (0x80 + Random.nextInt() % (0xBF - 0x80 + 1)).toByte()
|
||||
}
|
||||
}
|
||||
}
|
||||
return String(data, 0, i)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user