These benchmark results taken from
electhomas' repo, have a quite interesting result (new serialization formats). Let's see how much serialization cost, the fastest:
Format | type | ns/op | bytes/op | allocs/op | ns/alloc |
Mum | ser | 97 | 48 | 0 | 0.00 |
GencodeUnsafe | ser | 98 | 46 | 48 | 2.05 |
Colfer | ser | 124 | 51 | 64 | 1.94 |
Bebop | ser | 124 | 55 | 64 | 1.94 |
Gotiny | ser | 130 | 48 | 0 | 0.00 |
GotinyNoTime | ser | 136 | 48 | 0 | 0.00 |
Gogoprotobuf | ser | 147 | 53 | 64 | 2.30 |
XDR2 | ser | 159 | 60 | 64 | 2.48 |
Msgp | ser | 174 | 97 | 128 | 1.36 |
Gencode | ser | 186 | 53 | 80 | 2.33 |
FlatBuffers | ser | 298 | 95 | 0 | 0.00 |
Goprotobuf | ser | 317 | 53 | 64 | 4.95 |
CapNProto | ser | 386 | 96 | 56 | 6.89 |
CapNProto2 | ser | 586 | 96 | 244 | 2.40 |
Hprose2 | ser | 604 | 85 | 0 | 0.00 |
Ikea | ser | 670 | 55 | 72 | 9.31 |
ShamatonArrayMsgpack | ser | 758 | 50 | 176 | 4.31 |
Protobuf | ser | 801 | 52 | 152 | 5.27 |
ShamatonMapMsgpack | ser | 819 | 92 | 208 | 3.94 |
Gob | ser | 834 | 63 | 48 | 17.38 |
Hprose | ser | 915 | 85 | 402 | 2.28 |
GoAvro2Binary | ser | 950 | 47 | 488 | 1.95 |
VmihailencoMsgpack | ser | 1116 | 100 | 400 | 2.79 |
Bson | ser | 1171 | 110 | 392 | 2.99 |
Binary | ser | 1364 | 61 | 320 | 4.26 |
UgorjiCodecMsgpack | ser | 1422 | 91 | 1312 | 1.08 |
UgorjiCodecBinc | ser | 1468 | 95 | 1328 | 1.11 |
EasyJson | ser | 1789 | 149 | 895 | 2.00 |
XDR | ser | 1836 | 92 | 456 | 4.03 |
Json | ser | 2212 | 150 | 208 | 10.63 |
JsonIter | ser | 2392 | 139 | 248 | 9.65 |
GoAvro | ser | 2828 | 47 | 1008 | 2.81 |
Sereal | ser | 2936 | 132 | 904 | 3.25 |
GoAvro2Text | ser | 2975 | 134 | 1320 | 2.25 |
SSZNoTimeNoStringNoFloatA | ser | 4922 | 55 | 440 | 11.19 |
As we can see, Mum, Gencode, Colfer, Bebop, Gotiny, XDR2, MsgPack wins in terms of performace, in cost of serialization size. Let's check the deserialization performace, the fastest are:
Format | type | ns/op | bytes/op | allocs/op | ns/alloc |
Bebop | des | 104 | 55 | 32 | 3.25 |
XDR2 | des | 131 | 60 | 32 | 4.09 |
GencodeUnsafe | des | 161 | 46 | 96 | 1.68 |
Colfer | des | 197 | 50 | 112 | 1.76 |
Mum | des | 216 | 48 | 80 | 2.70 |
Gencode | des | 222 | 53 | 112 | 1.98 |
Gogoprotobuf | des | 230 | 53 | 96 | 2.40 |
GotinyNoTime | des | 241 | 48 | 96 | 2.51 |
FlatBuffers | des | 265 | 95 | 112 | 2.37 |
Gotiny | des | 267 | 48 | 112 | 2.38 |
Msgp | des | 314 | 97 | 112 | 2.80 |
CapNProto | des | 443 | 96 | 200 | 2.21 |
Goprotobuf | des | 481 | 53 | 168 | 2.86 |
ShamatonArrayMsgpack | des | 483 | 50 | 144 | 3.35 |
Hprose2 | des | 609 | 85 | 144 | 4.23 |
ShamatonMapMsgpack | des | 738 | 92 | 144 | 5.12 |
CapNProto2 | des | 778 | 96 | 320 | 2.43 |
Protobuf | des | 790 | 52 | 192 | 4.11 |
Ikea | des | 871 | 55 | 160 | 5.44 |
Gob | des | 900 | 63 | 112 | 8.04 |
GoAvro2Binary | des | 1092 | 47 | 560 | 1.95 |
Hprose | des | 1195 | 85 | 319 | 3.75 |
UgorjiCodecMsgpack | des | 1398 | 91 | 496 | 2.82 |
Binary | des | 1511 | 61 | 320 | 4.72 |
UgorjiCodecBinc | des | 1587 | 95 | 656 | 2.42 |
Bson | des | 1694 | 110 | 232 | 7.30 |
VmihailencoMsgpack | des | 1722 | 100 | 416 | 4.14 |
EasyJson | des | 1724 | 150 | 288 | 5.99 |
JsonIter | des | 1874 | 139 | 264 | 7.10 |
XDR | des | 2255 | 90 | 235 | 9.60 |
GoAvro2Text | des | 2826 | 134 | 799 | 3.54 |
Sereal | des | 3377 | 132 | 1008 | 3.35 |
Json | des | 4574 | 149 | 391 | 11.70 |
GoAvro | des | 6962 | 47 | 3328 | 2.09 |
SSZNoTimeNoStringNoFloatA | des | 7694 | 55 | 1392 | 5.53 |
In this part, Bebop, XDR2, Gencode, Colfer, Mum, Gogoprotobuf, Gotiny, FlatBuffers, MsgPack wins this part. So if your bandwidth is unlimited, you can choose these format as your serialization format. You can access the reformatted spreadsheet
here. Here's combined result of serialization and deserialization and addition of serialization size and allocation needed to deserialize.
Format | ns/op | bytes |
Bebop | 228 | 110 |
GencodeUnsafe | 259 | 92 |
XDR2 | 290 | 120 |
Mum | 313 | 96 |
Colfer | 321 | 101 |
GotinyNoTime | 377 | 96 |
Gogoprotobuf | 377 | 106 |
Gotiny | 397 | 96 |
Gencode | 408 | 106 |
Msgp | 488 | 194 |
FlatBuffers | 563 | 190 |
Goprotobuf | 798 | 106 |
CapNProto | 829 | 192 |
Hprose2 | 1,213 | 170 |
ShamatonArrayMsgpack | 1,241 | 100 |
CapNProto2 | 1,364 | 192 |
Ikea | 1,541 | 110 |
ShamatonMapMsgpack | 1,557 | 184 |
Protobuf | 1,591 | 104 |
Gob | 1,734 | 126 |
GoAvro2Binary | 2,042 | 94 |
Hprose | 2,110 | 170 |
UgorjiCodecMsgpack | 2,820 | 182 |
VmihailencoMsgpack | 2,838 | 200 |
Bson | 2,865 | 220 |
Binary | 2,875 | 122 |
UgorjiCodecBinc | 3,055 | 190 |
EasyJson | 3,513 | 299 |
XDR | 4,091 | 182 |
JsonIter | 4,266 | 278 |
GoAvro2Text | 5,801 | 268 |
Sereal | 6,313 | 264 |
Json | 6,786 | 299 |
GoAvro | 9,790 | 94 |
SSZNoTimeNoStringNoFloatA | 12,616 | 110 |
Here are the links for high performing libraries used:
- Bebop codegen from .bop (need to create schema like protobuf)
- Gencode codegen from .schema (similar to go syntax)
- XDR2 codegen version (other libs are using reflection/automatic), unmaintained
- Mum manual (must create the method to serialize and deserialize yourself), new name is enkodo
- Colfer codegen from .colf (similar to go syntax)
- Gogoprotobuf codegen from .proto
- Gotiny codegen, not recommended for production
- MsgPack codegen version (other libs are using reflection/automatic), from .go source file using go:generate, supported in lots of language
- FlatBuffers codegen, can be used in gRPC
What are the difference between codegen, automatic, manual?
- codegen means there are step to generate golang function by writing a schema definition file then run a program to convert that file to specific programming language implementation, sometimes using other format (so you cannot add custom tag to generated .go file), sometimes using golang struct with tag (like codegen version of msgpack above you need to label each property with `msg:"foo"`, so you can add your own custom tag on the struct property, eg. `json:"bar,omitempty" form:"baz" bson:"blabla"`)
- manual means you must write the serialization and deserialization yourself for each property of the struct (the library only the helper), this allows highly flexible system, so for example you read 1 byte first, then if the value is 1 then you read a string, if 2 you read int32, and so on. This can also be useful to parse network packet if having the same endian.
- automatic means you don't need to write any schema, it uses reflection so should be slower than codegen version.