Comparison performance testing between Pbandk and Kotlinx Protobuf

Introduction

This article is a comparison of the performance of the serialization and deserialization of the same data using the Pbandk library and the kotlinx-protobuf library.

Test data

@file:OptIn(PublicForGeneratedCode::class)

import kotlinx.serialization.Serializable

import kotlinx.serialization.decodeFromByteArray

import kotlinx.serialization.encodeToByteArray

import kotlinx.serialization.protobuf.ProtoBuf

import kotlinx.serialization.protobuf.ProtoNumber

import pbandk.*

import kotlin.time.measureTime

@Serializable

data class KotlinxTest(

    @ProtoNumber(1) val i: UInt = 0u,

    @ProtoNumber(2) val l: ULong = 0u,

    @ProtoNumber(3) val bytes: ByteArray,

)

@pbandk.Export

data class PbandkTest(

    val i: Int = 0,

    val l: Long = 0,

    val bytes: ByteArr? = null,

) : Message {

    override val unknownFields: Map<Int, pbandk.UnknownField> = emptyMap()

    override val descriptor: MessageDescriptor<PbandkTest> get() = Companion.descriptor

    override val protoSize: Int by lazy { super.protoSize }

    override fun plus(other: Message?): Message = this

    companion object : Message.Companion<PbandkTest> {

        override fun decodeWith(u: pbandk.MessageDecoder): PbandkTest = PbandkTest.decodeWithImpl(u)

        override val descriptor: MessageDescriptor<PbandkTest> by lazy {

            val fieldsList = ArrayList<FieldDescriptor<PbandkTest, *>>(3).apply {

                add(

                    FieldDescriptor(

                        messageDescriptor = this@Companion::descriptor,

                        name = "i",

                        number = 1,

                        type = FieldDescriptor.Type.Primitive.UInt32(),

                        jsonName = "i",

                        value = PbandkTest::i

                    )

                )

                add(

                    FieldDescriptor(

                        messageDescriptor = this@Companion::descriptor,

                        name = "l",

                        number = 2,

                        type = FieldDescriptor.Type.Primitive.UInt64(),

                        jsonName = "l",

                        value = PbandkTest::l

                    )

                )

                add(

                    FieldDescriptor(

                        messageDescriptor = this@Companion::descriptor,

                        name = "bytes",

                        number = 3,

                        type = FieldDescriptor.Type.Primitive.Bytes(),

                        jsonName = "bytes",

                        value = PbandkTest::bytes

                    )

                )

            }

            MessageDescriptor(

                fullName = "PbandkTest",

                messageClass = PbandkTest::class,

                messageCompanion = this,

                fields = fieldsList

            )

        }

    }

}

@Suppress("UNCHECKED_CAST")

private fun PbandkTest.Companion.decodeWithImpl(u: pbandk.MessageDecoder): PbandkTest {

    var i = 0

    var j = 0L

    var bytes: ByteArr? = null

    val unknownFields = u.readMessage(this) { fieldNumber, fieldValue ->

        when (_fieldNumber) {

            1 -> i = _fieldValue as Int

            2 -> j = _fieldValue as Long

            3 -> bytes = _fieldValue as ByteArr

        }

    }

    return PbandkTest(i, j, bytes)

}

suspend fun main(args : Array<String>){

    println("KTX_PB: " + measureTime {

        var i = 1u

        repeat(10000000) {

            val bytes = ProtoBuf.encodeToByteArray(KotlinxTest(i, 2u, byteArrayOf(1, 2, 3)))

            val obj = ProtoBuf.decodeFromByteArray<KotlinxTest>(bytes)

            i += obj.i

        }

    })

    println("PBDANK_PB: " + measureTime {

        var i = 1

        repeat(10000000) {

            val bytes = PbandkTest(i, 2, ByteArr(byteArrayOf(1, 2, 3))).encodeToByteArray()

            val obj = PbandkTest.decodeFromByteArray(bytes)

            i += obj.i

        }

    })

}

Test result

# First

13:29:11: 正在执行 'jvmRun -DmainClass=MainJvmKt --quiet'…

KTX_PB: 697.140600ms

PBDANK_PB: 1.191587100s

13:29:14: 执行完成 'jvmRun -DmainClass=MainJvmKt --quiet'。

# Second

13:33:15: 正在执行 'jvmRun -DmainClass=MainJvmKt --quiet'…

KTX_PB: 687.145800ms

PBDANK_PB: 1.180827900s

13:33:18: 执行完成 'jvmRun -DmainClass=MainJvmKt --quiet'。

Conclusion

The kotlinx-protobuf library is faster than the Pbandk library in serialization and deserialization.

Moreover, pbandk's support for various types of kotlin is not friendly, and it belongs to a garbage project written by kotlin.

I found a lot of use of this garbage serialization library in Tencent's kuikly project. kotlinx protobuf does not provide the function of one click conversion from proto to kotlin data class.

I guess Tencent's so-called programmers used this ugly, high-performance, and extremely high garbage proto serialization framework to save effort.