🐈

Benchmark Jackson and Protobuf

2023/10/16に公開2

検証内容について

Protobufがどの程度高速なのかを Java Json Serialize/Deserialize の デファクトスタンダードである Jackson と比較をして確認してみる

Protobuf
Jackson

Benchmark Program

  • build.gradle.kts

version の指定は parent project にて実施しているため、以下の設定の中にはversion情報がない

plugins {
    id("me.champeau.jmh")
    id("com.google.protobuf")
}

dependencies {
    implementation("com.google.protobuf:protobuf-java")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.openjdk.jmh:jmh-generator-annprocess")
}
  • message.proto
syntax = "proto3";
message SampleMessage {
    string name = 1;
    int32 age = 2;
}
  • SerializerBenchmark
@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 5) //
@Measurement(iterations = 10) // CNT
open class SerializerBenchmark {

    data class SampleJsonMessage(
        val name: String,
        val age: Long,
    )

    private val messageBuf = Sample.SampleMessage.newBuilder().setName("John").setAge(25).build()
    private val messageJson = SampleJsonMessage(name = "John", age = 25)
    private val objectMapper = ObjectMapper().registerModule(
        KotlinModule.Builder().build(),
    )

    @Benchmark
    fun protobufSerialize(): ByteArray {
        return messageBuf.toByteArray()
    }

    @Benchmark
    fun protobufDeserialize() {
        Sample.SampleMessage.parseFrom(messageBuf.toByteArray())
    }

    @Benchmark
    fun jsonSerialize(): String {
        return objectMapper.writeValueAsString(messageJson)
    }

    @Benchmark
    fun jsonDeserialize() {
        objectMapper.readValue(objectMapper.writeValueAsString(messageJson), SampleJsonMessage::class.java)
    }
}

Benchmark Result

Benchmark                                 Mode  Cnt         Score         Error  Units
SerializerBenchmark.jsonDeserialize      thrpt   10   1413727.543 ±  525960.912  ops/s
SerializerBenchmark.jsonSerialize        thrpt   10   6571743.362 ± 1307144.645  ops/s
SerializerBenchmark.protobufDeserialize  thrpt   10  18834307.366 ± 3637475.851  ops/s
SerializerBenchmark.protobufSerialize    thrpt   10  41709114.574 ± 1426671.240  ops/s

Protobuf vs JSON:

  • Protobuf Serialize は、Jackson Serialize よりも約6.3倍高速
  • Protobuf Deserialize は、Jackson Deserialize よりも約13.3倍高速

Summary

  • Protobufは、serialize/deserialize の両方で Jackson よりも高速
    • 特に、大量のデータを高速に処理する必要がある場合や、ネットワーク帯域を節約したい場合には、Protobufを使用することが推奨される
  • しかし、可読性やデバッグの容易さを優先する場合は、JSON を使用することが適している

エラーマージン:

  • エラーマージンも考慮すると、Protobufのパフォーマンスは一貫して高いが、特に Deserialize のエラーマージンが大きいので、ベンチマークの環境や条件によっては変動があるかも

Etc

  • FlatBuffers は protobuf と比べても、より速いようなので、検証してみること
  • エラーマージンについて
    • ベンチマークの実行中に得られた結果の変動や不確実性を示すもの
      • 具体的には、同じ条件でベンチマークを複数回実行した場合、その結果がどれほど一貫しているか(または変動しているか)を示す指標

Discussion

wrongwrongwrongwrong

JSONとProtobufの処理コスト差を比較することが主目的だと思いますが、であればライブラリの選定が適切でないように見えます。
google.protobufはコード生成を用いているのに対し、Jacksonはリフレクションベースのため、その時点で性能差は大きいです。
比較対象にするなら、同じくコード生成を用いるmoshiやkotlinx.serializationの方が適切に見えます。

利用したことは有りませんが、kotlinx.serializationはJSONとProtobuf両方に対応しているようなので、そちらを用いた方がよりフェアな比較になるのではないかと感じました。

kackeykackey

コメントありがとうございます。

google.protobufはコード生成を用いているのに対し、Jacksonはリフレクションベースのため、その時点で性能差は大きいです。

こちらはおっしゃる通りになります。
今回現場で利用しているlibraryがjacksonであったため選定したという理由でした。
コード生成のアプローチをしているライブラリでも試してみたいと思います!