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:
Paulo Pinheiro
2021-01-28 23:49:25 +01:00
committed by GitHub
parent 13d9e35858
commit 6d91096a2f
25 changed files with 4772 additions and 0 deletions

View File

@@ -0,0 +1 @@
../../../../java/

View File

@@ -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())
}
}
}

View File

@@ -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)
}
}