-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
480 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Kotlin Avro Benchmark | ||
|
||
This project contains a benchmark that compares the serialization / deserialization performance of the following avro libraries: | ||
|
||
- [Avro4k](https://github.com/avro-kotlin/avro4k/) | ||
- [Jackson Avro](https://github.com/FasterXML/jackson-dataformats-binary/tree/master/avro) | ||
- Coming soon: [Avro](https://avro.apache.org/) | ||
|
||
## Results | ||
|
||
<details> | ||
<summary>Macbook air M2</summary> | ||
|
||
``` | ||
Benchmark Mode Cnt Score Error Units | ||
Avro4kClientsBenchmark.read thrpt 2 439983.130 ops/s | ||
Avro4kClientsBenchmark.write thrpt 2 474453.236 ops/s | ||
JacksonAvroClientsBenchmark.read thrpt 2 577757.798 ops/s | ||
JacksonAvroClientsBenchmark.write thrpt 2 649982.820 ops/s | ||
``` | ||
|
||
For the moment, Jackson Avro is faster than Avro4k because Avro4k is still not doing direct encoding so there is an intermediate generic data step. | ||
|
||
</details> | ||
|
||
## Run the benchmark locally | ||
|
||
Just execute the benchmark: | ||
|
||
```shell | ||
../gradlew benchmark | ||
``` | ||
|
||
You can get the results in the `build/reports/benchmarks/main` directory. | ||
|
||
## Other information | ||
|
||
Thanks for [@twinprime](https://github.com/twinprime) for this initiative. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
plugins { | ||
java | ||
kotlin("jvm") version libs.versions.kotlin | ||
id("org.jetbrains.kotlinx.benchmark") version "0.4.10" | ||
kotlin("plugin.allopen") version libs.versions.kotlin | ||
kotlin("plugin.serialization") version libs.versions.kotlin | ||
} | ||
|
||
allOpen { | ||
annotation("org.openjdk.jmh.annotations.State") | ||
} | ||
|
||
benchmark { | ||
configurations { | ||
named("main") { | ||
reportFormat = "text" | ||
} | ||
} | ||
targets { | ||
register("main") | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation("org.apache.commons:commons-lang3:3.14.0") | ||
implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:0.4.10") | ||
|
||
val jacksonVersion = "2.17.0" | ||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") | ||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion") | ||
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-avro:$jacksonVersion") | ||
|
||
implementation(project(":")) | ||
} |
51 changes: 51 additions & 0 deletions
51
benchmark/src/main/kotlin/com/github/avrokotlin/benchmark/Avro4kClientsBenchmark.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.github.avrokotlin.benchmark | ||
|
||
import com.github.avrokotlin.avro4k.Avro | ||
import com.github.avrokotlin.avro4k.decodeFromByteArray | ||
import com.github.avrokotlin.avro4k.encodeToByteArray | ||
import kotlinx.benchmark.Benchmark | ||
|
||
internal class Avro4kClientsStaticReadBenchmark { | ||
fun main() { | ||
Avro4kClientsBenchmark().apply { | ||
initTestData() | ||
for (i in 0 until 1000000) { | ||
if (i % 100000 == 0) println("Iteration $i") | ||
read() | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal class Avro4kClientsStaticWriteBenchmark { | ||
fun main() { | ||
Avro4kClientsBenchmark().apply { | ||
initTestData() | ||
for (i in 0 until 1000000) { | ||
if (i % 100000 == 0) println("Iteration $i") | ||
write() | ||
} | ||
} | ||
} | ||
} | ||
|
||
internal class Avro4kClientsBenchmark : SerializationBenchmark() { | ||
lateinit var data: ByteArray | ||
var writeMode = false | ||
|
||
override fun prepareBinaryData() { | ||
data = Avro.encodeToByteArray(clients) | ||
} | ||
|
||
@Benchmark | ||
fun read() { | ||
if (writeMode) writeMode = false | ||
Avro.decodeFromByteArray<Clients>(data) | ||
} | ||
|
||
@Benchmark | ||
fun write() { | ||
if (!writeMode) writeMode = true | ||
Avro.encodeToByteArray(clients) | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
benchmark/src/main/kotlin/com/github/avrokotlin/benchmark/Clients.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.github.avrokotlin.benchmark | ||
|
||
import com.github.avrokotlin.avro4k.AvroDecimal | ||
import kotlinx.serialization.Contextual | ||
import kotlinx.serialization.Serializable | ||
import java.math.BigDecimal | ||
import java.time.Instant | ||
import java.time.LocalDate | ||
import java.util.* | ||
|
||
@Serializable | ||
internal data class Clients( | ||
var clients: MutableList<Client> = mutableListOf() | ||
) | ||
@Serializable | ||
internal data class Client( | ||
var id: Long = 0, | ||
var index: Int = 0, | ||
@Contextual | ||
var guid: UUID? = null, | ||
var isActive: Boolean = false, | ||
@Contextual | ||
@AvroDecimal(5,10) | ||
var balance: BigDecimal? = null, | ||
var picture: ByteArray? = null, | ||
var age: Int = 0, | ||
var eyeColor: EyeColor? = null, | ||
var name: String? = null, | ||
var gender: String? = null, | ||
var company: String? = null, | ||
var emails: Array<String> = emptyArray(), | ||
var phones: LongArray = LongArray(0), | ||
var address: String? = null, | ||
var about: String? = null, | ||
@Contextual | ||
var registered: LocalDate? = null, | ||
var latitude : Double = 0.0, | ||
var longitude: Double = 0.0, | ||
var tags: List<String> = emptyList(), | ||
var partners: List<Partner> = emptyList(), | ||
) | ||
|
||
@Serializable | ||
internal enum class EyeColor { | ||
BROWN, | ||
BLUE, | ||
GREEN; | ||
} | ||
@Serializable | ||
internal class Partner( | ||
val id: Long = 0, | ||
val name: String? = null, | ||
@Contextual | ||
val since: Instant? = null | ||
) |
62 changes: 62 additions & 0 deletions
62
benchmark/src/main/kotlin/com/github/avrokotlin/benchmark/JacksonAvroClientsBenchmark.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package com.github.avrokotlin.benchmark | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import com.fasterxml.jackson.databind.ObjectReader | ||
import com.fasterxml.jackson.databind.ObjectWriter | ||
import com.fasterxml.jackson.dataformat.avro.AvroFactory | ||
import com.fasterxml.jackson.dataformat.avro.AvroMapper | ||
import com.fasterxml.jackson.dataformat.avro.jsr310.AvroJavaTimeModule | ||
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator | ||
import com.fasterxml.jackson.module.kotlin.kotlinModule | ||
import com.fasterxml.jackson.module.kotlin.registerKotlinModule | ||
import kotlinx.benchmark.Benchmark | ||
import kotlinx.benchmark.Setup | ||
|
||
internal class JacksonAvroClientsBenchmark : SerializationBenchmark() { | ||
lateinit var writer: ObjectWriter | ||
lateinit var reader: ObjectReader | ||
|
||
lateinit var data: ByteArray | ||
var writeMode = false | ||
|
||
@Setup | ||
fun setup() { | ||
val schemaMapper = ObjectMapper(AvroFactory()) | ||
.registerKotlinModule() | ||
.registerModule(AvroJavaTimeModule()) | ||
writer = Clients::class.java.createWriter(schemaMapper) | ||
reader = Clients::class.java.createReader(schemaMapper) | ||
} | ||
|
||
override fun prepareBinaryData() { | ||
data = writer.writeValueAsBytes(clients) | ||
} | ||
|
||
@Benchmark | ||
fun read() { | ||
if (writeMode) writeMode = false | ||
reader.readValue<Clients>(data) | ||
} | ||
|
||
@Benchmark | ||
fun write() { | ||
if (!writeMode) writeMode = true | ||
writer.writeValueAsBytes(clients) | ||
} | ||
} | ||
|
||
private fun <T> Class<T>.createWriter(schemaMapper: ObjectMapper): ObjectWriter { | ||
val gen = AvroSchemaGenerator() | ||
schemaMapper.acceptJsonFormatVisitor(this, gen) | ||
|
||
val mapper = AvroMapper().registerModule(kotlinModule()).registerModule(AvroJavaTimeModule()) | ||
return mapper.writer(gen.generatedSchema) | ||
} | ||
|
||
private fun <T> Class<T>.createReader(schemaMapper: ObjectMapper): ObjectReader { | ||
val gen = AvroSchemaGenerator() | ||
schemaMapper.acceptJsonFormatVisitor(this, gen) | ||
|
||
val mapper = AvroMapper().registerModule(kotlinModule()).registerModule(AvroJavaTimeModule()) | ||
return mapper.reader(gen.generatedSchema).forType(this) | ||
} |
23 changes: 23 additions & 0 deletions
23
benchmark/src/main/kotlin/com/github/avrokotlin/benchmark/SerializationBenchmark.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.github.avrokotlin.benchmark | ||
|
||
import com.github.avrokotlin.benchmark.gen.ClientsGenerator | ||
import kotlinx.benchmark.* | ||
import java.util.concurrent.TimeUnit | ||
|
||
|
||
@State(Scope.Benchmark) | ||
@Warmup(iterations = 3) | ||
@BenchmarkMode(Mode.Throughput) | ||
@Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) | ||
internal abstract class SerializationBenchmark { | ||
lateinit var clients: Clients | ||
|
||
@Setup | ||
fun initTestData(){ | ||
clients = Clients() | ||
ClientsGenerator.populate(clients, 1000) | ||
prepareBinaryData() | ||
} | ||
|
||
abstract fun prepareBinaryData() | ||
} |
116 changes: 116 additions & 0 deletions
116
benchmark/src/main/kotlin/com/github/avrokotlin/benchmark/gen/ClientsGenerator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package com.github.avrokotlin.benchmark.gen | ||
|
||
import com.github.avrokotlin.benchmark.Client | ||
import com.github.avrokotlin.benchmark.Clients | ||
import com.github.avrokotlin.benchmark.EyeColor | ||
import com.github.avrokotlin.benchmark.Partner | ||
|
||
import java.time.LocalDate | ||
import java.time.OffsetDateTime | ||
import java.time.ZoneOffset | ||
|
||
internal object ClientsGenerator { | ||
fun populate(obj: Clients, size: Int): Int { | ||
var approxSize = 14 // {'clients':[]} | ||
|
||
obj.clients = mutableListOf() | ||
while (approxSize < size) { | ||
approxSize += appendClient(obj, size - approxSize) | ||
approxSize += 1 // , | ||
} | ||
return approxSize | ||
} | ||
|
||
private fun appendClient(uc: Clients, sizeAvailable: Int): Int { | ||
var expectedSize = 2 // {} | ||
val u = Client() | ||
u.id = Math.abs(RandomUtils.nextLong()) | ||
expectedSize += 9 + u.id.toString().length // ,'id':'' | ||
u.index = (RandomUtils.nextInt(0, Int.MAX_VALUE)) | ||
expectedSize += 11 + u.index.toString().length // ,'index':'' | ||
u.guid = (RandomUtils.nextUUID()) | ||
expectedSize += 10 + 36 // ,'guid':'' | ||
u.isActive = (RandomUtils.nextInt(0, 2) == 1) | ||
expectedSize += 17 + if (u.isActive) 4 else 5 // ,'isActive':'' | ||
u.balance = (RandomUtils.randomBigDecimal()) | ||
expectedSize += 16 + u.balance!!.toPlainString().length // ,'balance':'' | ||
u.picture = RandomUtils.randomBytes(4048) | ||
expectedSize += 16 + u.picture!!.size // ,'picture':'' | ||
u.age = (RandomUtils.nextInt(0, 100)) | ||
expectedSize += 9 + u.age.toString().length // ,'age':'' | ||
u.eyeColor = (EyeColor.entries[RandomUtils.nextInt(3)]) | ||
expectedSize += 17 + u.eyeColor!!.name.length // ,'eyeColor':'' | ||
u.name = (RandomUtils.randomAlphanumeric(20)) | ||
expectedSize += 10 + u.name!!.length // ,'name':'' | ||
u.gender = (RandomUtils.randomAlphanumeric(20)) | ||
expectedSize += 12 + u.gender!!.length // ,'gender':'' | ||
u.company = (RandomUtils.randomAlphanumeric(20)) | ||
expectedSize += 13 + u.company!!.length // ,'company':'' | ||
u.emails = (RandomUtils.stringArray(RandomUtils.nextInt(10), 20)) | ||
var calcSize = 0 | ||
for (e in u.emails) { | ||
calcSize += 3 + e.length | ||
} | ||
expectedSize += 11 + calcSize // ,'email':'' | ||
u.phones = (RandomUtils.longArray(RandomUtils.nextInt(10))) | ||
calcSize = 0 | ||
for (p in u.phones) { | ||
calcSize += 1 + p.toString().length | ||
} | ||
expectedSize += 11 + calcSize // ,'phone':'' | ||
u.address = (RandomUtils.randomAlphanumeric(20)) | ||
expectedSize += 13 + u.address!!.length // ,'address':'' | ||
u.about = (RandomUtils.randomAlphanumeric(20)) | ||
expectedSize += 11 + u.about!!.length // ,'about':'' | ||
u.registered = ( | ||
LocalDate.of( | ||
1900 + RandomUtils.nextInt(110), | ||
1 + RandomUtils.nextInt(12), | ||
1 + RandomUtils.nextInt(28) | ||
) | ||
) | ||
expectedSize += 16 + 10 // ,'registered':'' | ||
u.latitude = (RandomUtils.nextDouble(0.0, 90.0)) | ||
expectedSize += 14 + u.latitude.toString().length // ,'latitude':'' | ||
u.longitude = (RandomUtils.nextDouble(0.0, 180.0)) | ||
expectedSize += 15 + u.longitude.toString().length // ,'longitude':'' | ||
val tags = mutableListOf<String>() | ||
expectedSize += 10 // ,'tags':[] | ||
val nTags: Int = RandomUtils.nextInt(0, 50) | ||
for (i in 0 until nTags) { | ||
if (expectedSize > sizeAvailable) { | ||
break | ||
} | ||
val t: String = RandomUtils.randomAlphanumeric(10) | ||
tags.add(t) | ||
expectedSize += t.length // '', | ||
} | ||
u.tags = tags | ||
val nPartners: Int = RandomUtils.nextInt(0, 30) | ||
val partners = mutableListOf<Partner>() | ||
expectedSize += 13 // ,'partners':[] | ||
for (i in 0 until nPartners) { | ||
if (expectedSize > sizeAvailable) { | ||
break | ||
} | ||
val id: Long = RandomUtils.nextLong() | ||
val name: String = RandomUtils.randomAlphabetic(30) | ||
val at = OffsetDateTime.of( | ||
1900 + RandomUtils.nextInt(110), | ||
1 + RandomUtils.nextInt(12), | ||
1 + RandomUtils.nextInt(28), | ||
RandomUtils.nextInt(24), | ||
RandomUtils.nextInt(60), | ||
RandomUtils.nextInt(60), | ||
RandomUtils.nextInt(1000000000), | ||
ZoneOffset.UTC | ||
).toInstant() | ||
partners.add(Partner(id, name, at)) | ||
expectedSize += id.toString().length + name.length + 50 // {'id':'','name':'','since':''}, | ||
} | ||
u.partners = partners | ||
uc.clients.add(u) | ||
return expectedSize | ||
} | ||
} | ||
|
Oops, something went wrong.