Go の msgpack ライブラリ比較


Go で msgpack を扱うには github.com/ugorji/go/codec を使っているのですが、最近 github.com/vmihailenco/msgpack を見つけてちょっとよさそうだったのでベンチマークを取ってみました。

## コード
```go
package main

import (
	"bytes"
	"os"
	"testing"
	"time"

	"github.com/ugorji/go/codec"
	"github.com/vmihailenco/msgpack"
)

type Event struct {
	Tag    string    `codec:"tag" msgpack:"tag"`
	Time   time.Time `codec:"time" msgpack:"time"`
	Record map[string]interface{}
}

var (
	mh    = &codec.MsgpackHandle{RawToString: true}
	event *Event
)

func init() {
	event = &Event{
		Tag:  "sysstat.process",
		Time: time.Now(),
		Record: map[string]interface{}{
			"pid":      12353,
			"name":     "fluxion",
			"cmd":      "fluxion -c fluxion.toml",
			"rss":      3563520,
			"vms":      11005952,
			"shared":   2666496,
			"cpu_time": 80,
		},
	}
}

func BenchmarkCodecEncode(b *testing.B) {
	buf := &bytes.Buffer{}
	enc := codec.NewEncoder(buf, mh)
	for i := 0; i < b.N; i++ {
		enc.Encode(event)
		buf.Reset()
	}
}

func BenchmarkCodecDecode(b *testing.B) {
	buf := &bytes.Buffer{}
	codec.NewEncoder(buf, mh).Encode(event)
	r := bytes.NewReader(buf.Bytes())
	b.ResetTimer()

	var ev Event
	dec := codec.NewDecoder(r, mh)
	for i := 0; i < b.N; i++ {
		dec.Decode(&ev)
		r.Seek(0, os.SEEK_SET)
	}
}

func BenchmarkMsgpackEncode(b *testing.B) {
	buf := &bytes.Buffer{}
	enc := msgpack.NewEncoder(buf)
	for i := 0; i < b.N; i++ {
		enc.Encode(event)
		buf.Reset()
	}
}

func BenchmarkMsgpackDecode(b *testing.B) {
	buf := &bytes.Buffer{}
	msgpack.NewEncoder(buf).Encode(event)
	r := bytes.NewReader(buf.Bytes())
	b.ResetTimer()

	var ev Event
	dec := msgpack.NewDecoder(r)
	for i := 0; i < b.N; i++ {
		dec.Decode(&ev)
		r.Seek(0, os.SEEK_SET)
	}
}
```

## 実行結果
```
$ go test -bench .
testing: warning: no tests to run
PASS
BenchmarkCodecEncode     1000000              1970 ns/op
BenchmarkCodecDecode     1000000              2543 ns/op
BenchmarkMsgpackEncode    500000              5274 ns/op
BenchmarkMsgpackDecode    500000              6793 ns/op
ok      _/Users/yoshihisa/junk/msgpack  10.761s
```

github.com/ugorji/go/codec の方がだいぶ速いですね。

## まとめ
* github.com/ugorji/go/codec
    * 速い
    * ext type を使わずに Go の type と binary を変換する仕組みがある ※追記参照
    * ext type 対応
    * omitempty 対応
    * anonymous field のインライン化対応
* github.com/vmihailenco/msgpack
    * コードが読みやすい
    * ext type を使わずに Go の type と binary を変換する仕組みがある
    * ext type 非対応
    * omitempty 非対応
    * anonymous field のインライン化非対応

基本的には github.com/ugorji/go/codec を使うのが良さそうです。

~~github.com/vmihailenco/msgpack の方は、例えば time.Time を int64 型で表現したいけど ext type を定義したくない時とかに使えそうですね。~~ただし decode する時に型のガイドが必要になるので、interface{} 型には decode できなくなってしまいますが。

## 追記
ext type を使わずに Go の type と binary を相互変換する仕組みは github.com/ugorji/go/codec には無いと思っていましたが、よく見たらありました。以下のインタフェースを実装しておけばいいようです。

```go
type binaryUnmarshaler interface {
	UnmarshalBinary(data []byte) error
}

type binaryMarshaler interface {
	MarshalBinary() (data []byte, err error)
}
```

time.Time はこのインタフェースを実装しているので（それに合わせて定義した？）何もしなくても Go 同士だとよしなに扱えるようです。

なお、github.com/vmihailenco/msgpack の場合は以下のインタフェースを実装します。

```go
type encoder interface {
	EncodeMsgpack(io.Writer) error
}

type decoder interface {
	DecodeMsgpack(io.Reader) error
}
```