Struct
1
2
3
4
5
type ColorGroup struct {
Id int `json:"id" xml:"id,attr" msg:"id"`
Name string `json:"name" xml:"name" msg:"name"`
Colors []string `json:"colors" xml:"colors" msg:"colors"`
}
Colors

目前本项目包含了以下几种序列化库的性能比较:

对于序列化库的实现来讲,如果在运行时通过反射的方式进行序列化和反序列化,性能不会太好,比如官方库的Json和Xml序列化方法,所以高性能的序列化库很多都是通过代码生成在编译的时候提供序列化和反序列化的方法,下面我会介绍MessagePack和gencode两种性能较高的序列化库。

Thrift
1
2
3
4
5
6
7
namespace go gosercomp
struct ThriftColorGroup {
1: i32 id = 0,
2: string name,
3: list<string> colors,
}
flatbuffers
1
2
3
4
5
6
7
8
9
namespace gosercomp;
table FlatBufferColorGroup {
cgId:int (id: 0);
name:string (id: 1);
colors: [string] (id: 2);
}
root_type FlatBufferColorGroup;
protobuf
1
2
3
4
5
6
7
8
package gosercomp;
message ProtoColorGroup {
required int32 id = 1;
required string name = 2;
repeated string colors = 3;
}
IdNameColors

测试结果

完整的测试结果可以看这里,

以下是Json、Xml、Protobuf、MessagePack、gencode的性能数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
benchmark _name iter time/iter alloc bytes/iter allocs/iter
-------------------------------------------------------------------------------------------------------------------------
BenchmarkMarshalByJson-4 1000000 1909 ns/op 376 B/op 4 allocs/op
BenchmarkUnmarshalByJson-4 500000 4044 ns/op 296 B/op 9 allocs/op
BenchmarkMarshalByXml-4 200000 7893 ns/op 4801 B/op 12 allocs/op
BenchmarkUnmarshalByXml-4 100000 25615 ns/op 2807 B/op 67 allocs/op
BenchmarkMarshalByProtoBuf-4 2000000 969 ns/op 328 B/op 5 allocs/op
BenchmarkUnmarshalByProtoBuf-4 1000000 1617 ns/op 400 B/op 11 allocs/op
BenchmarkMarshalByMsgp-4 5000000 256 ns/op 80 B/op 1 allocs/op
BenchmarkUnmarshalByMsgp-4 3000000 459 ns/op 32 B/op 5 allocs/op
BenchmarkMarshalByGencode-4 20000000 66.4 ns/op 0 B/op 0 allocs/op
BenchmarkUnmarshalByGencode-4 5000000 271 ns/op 32 B/op 5 allocs/op

可以看出Json、Xml的序列化和反序列化性能是很差的。想比较而言MessagePack有10x的性能的提升,而gencode比MessagePack的性能还要好很多。

MessagePack的实现

MessagePack

上图是一个27个字节的JSON数据,如果使用MessagePack的话可以用18个字节就可以表示了。

82A7C3C2C000

完整的格式可以参照官方规范。

MessagePack支持多种开发语言。

题外话,一个较新的RFC规范 CBOR/rfc7049 (简洁的二进制对象表示)定义了一个类似的规范,可以表达更详细的内容。

推荐使用的Go MessagePack库是 tinylib/msgp,它比ugorji/go有更好的性能。

msgpmsgColorGroupgo generatemsgp_gen.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package gosercomp
// NOTE: THIS FILE WAS PRODUCED BY THE
// MSGP CODE GENERATION TOOL (github.com/tinylib/msgp)
// DO NOT EDIT
import (
"github.com/tinylib/msgp/msgp"
)
// MarshalMsg implements msgp.Marshaler
func (z *ColorGroup) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 3
// string "id"
o = append(o, 0x83, 0xa2, 0x69, 0x64)
o = msgp.AppendInt(o, z.Id)
// string "name"
o = append(o, 0xa4, 0x6e, 0x61, 0x6d, 0x65)
o = msgp.AppendString(o, z.Name)
// string "colors"
o = append(o, 0xa6, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x73)
o = msgp.AppendArrayHeader(o, uint32(len(z.Colors)))
for xvk := range z.Colors {
o = msgp.AppendString(o, z.Colors[xvk])
}
return
}
// UnmarshalMsg implements msgp.Unmarshaler
func (z *ColorGroup) UnmarshalMsg(bts []byte) (o []byte, err error) {
var field []byte
_ = field
var isz uint32
isz, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
return
}
for isz > 0 {
isz--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
return
}
switch msgp.UnsafeString(field) {
case "id":
z.Id, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
return
}
case "name":
……

生成的代码的使用类似官方库的Json和Xml,提供了Marshal和UnmarshalMsg的方法。

MarshalMsgzero alloc

反序列化的时候会读取字段的名字,再将相应的字节反序列化赋值给对象的相应的字段。

总体来说,MessagePack的性能已经相当高了,而且生成的数据也非常小,又是跨语言支持的,是值得关注的一个序列化库。

gencode

对于MessagePack还有没有可提升的空间?测试数据显示, andyleap/gencode的性能还要好,甚至于性能是MessagePack的两倍。

andyleap/gencode的目标也是提供快速而且数据很少的序列化库。
它定义了自有的数据格式,并提供工具生成Golang代码。

下面是我测试用的数据格式。

1
2
3
4
5
struct GencodeColorGroup {
Id int32
Name string
Colors []string
}

它提供了类似于Golang的数据类型struct,定义结构也类似, 并提供了一组数据类型。

你可以通过它的工具生成数据结构的代码:

1
gencode.exe go -schema=gencode.schema -package gosercomp
0x80
copy

反序列化的时候也是依次解析出各字段的值,因为在编译的时候已经知道每个字段的类型,所以gencode无需元数据,可以聪明的对字节按照流的方式顺序处理。

可以看出,gencode相对于MessagePack,本身并没有为数据中加入额外的元数据的信息,也无需写入字段的类型信息,这样也可以减少生成的数据大小,同时它不会对小整数、短字符串,小的Map进行刻意的压缩,减少的代码的复杂度和判断分支,代码更加的简练而高效。

值得注意的是,gencode生成的代码除了官方库外不依赖其它的第三方库。

从测试数据来看,它的性能更胜一筹。