Goの構造体のバイナリエンコーディングを速くしたい

この記事は HRBrain Advent Calendar 2021 2日目の記事です。

qiita.com

はじめに

Goで構造体をバイナリエンコーディングする際、大抵は encoding/jsonencoding/gob を使うと思います。

以下の記事を読んで、標準の encoding/binary パッケージを使えば任意の構造体のエンコード・デコード処理を自前で書けることを知りました。

zenn.dev

構造体のエンコード・デコード処理を自前で書くとどれくらい速くなるのか気になったので試してみました。

単純にreflectionを使わない分速くなりそうです。自前で書いたエンコード・デコードのパフォーマンスを、 encoding/json encoding/gob と比較してみます。

※最初に言っておきますが実用性は殆ど無いと思います。

型ごとのエンコード・デコード方法

int 系

上で紹介した記事のように encoding/binary の関数をそのまま使えます。

func IntEncode(i int) ([]byte, int) {
    // intの変換に必要な最大容量を確保
    out := make([]byte, binary.MaxVarintLen64)
    n := binary.PutVarint(out, int64(i))
    return out[:n], n
}

func IntDecode(in []byte) (int, int) {
    intRaw, intLen := binary.Varint(in)
    return int(intRaw), intLen
}

int8やint16などもint64にキャストすれば同じ方法で変換できます。

uint 系

intとほぼ同じです。

func UintEncode(u uint) ([]byte, int) {
    // uintの変換に必要な最大容量を確保
    out := make([]byte, binary.MaxVarintLen64)
    n := binary.PutUvarint(out, uint64(u))
    return out[:n], n
}

func UintDecode(in []byte) (uint, int) {
    uintRaw, uintLen := binary.Uvarint(in)
    return uint(uintRaw), uintLen
}

float 系

math パッケージを使います。

func FloatEncode(f float64) ([]byte, int) {
    out := make([]byte, binary.MaxVarintLen64)
    n := binary.PutUvarint(out, math.Float64bits(f))
    return out[:n], n
}

func FloatDecode(in []byte) (float64, int) {
    floatRaw, floatLen := binary.Uvarint(in)
    return math.Float64frombits(floatRaw), floatLen
}

string

文字列のバイト数と文字列を別々にセットします。エンコード時にバイト数をセットすることで、デコード時に読み取るバイト数が分かるようになります。

func StringEncode(str string) ([]byte, int) {
    n := 0
    strSize := len(str)
    // 文字列の長さの最大 + 文字列の長さ
    out := make([]byte, binary.MaxVarintLen64+strSize)
    // 文字列の長さをセット
    n += binary.PutUvarint(out, uint64(strSize))
    // 文字列をコピー
    copy(out[n:n+strSize], str)
    n += strSize
    return out[:n], n
}

func StringDecode(in []byte) (string, int) {
    n := 0
    // 文字列の長さを読み取る
    strLen, strLenLen := binary.Uvarint(in)
    n += strLenLen
    // 長さの分だけ文字列として読み取る
    str := string(in[n : n+int(strLen)])
    n += int(strLen)
    return str, n
}

bool

trueなら1、falseなら0をセットします。

func BoolEncode(b bool) ([]byte, int) {
    if b {
        return []byte{1}, 1
    }
    return []byte{0}, 1
}

func BoolDecode(in []byte) (bool, int) {
    return in[0] == 1, 1
}

01をセットするのに1バイト使っているのが少しもったいない(1ビットで十分のはず)気もします。

ポインタ

先頭1バイトにnilかどうかをセットします。

func PointerEncode(p *int) ([]byte, int) {
    n := 1
    if p == nil {
        // nilの場合は1バイト目に0をセット
        return []byte{0}, n
    }
    // nil判定 + intの最大
    out := make([]byte, n+binary.MaxVarintLen64)
    // nilでない場合は1バイト目に1をセット
    out[0] = 1
    n += binary.PutVarint(out[n:], int64(*p))
    return out[:n], n
}

func PointerDecode(in []byte) (*int, int) {
    n := 0
    // 1バイト目が0ならnilを返す
    isNotNil, isNotNilLen := binary.Uvarint(in[n:])
    n += isNotNilLen
    if isNotNil == 0 {
        return nil, n
    }
    intRaw, intLen := binary.Varint(in[n:])
    n += intLen
    t := int(intRaw)
    return &t, n
}

スライス

1バイト目にnilかどうかをセットし、次にスライスの長さをセットし、その後に各要素を詰めていきます。スライスの長さをセットするのは、デコード時にスライスの長さ分ループして読み取るためです。

func SliceEncode(ints []int) ([]byte, int) {
    n := 1
    if ints == nil {
        // nilの場合は1バイト目に0をセット
        return []byte{0}, n
    }
    intsSize := len(ints)
    // nil判定 + スライスの長さの最大 + スライスの要素数*1要素の最大
    out := make([]byte, n+binary.MaxVarintLen64+intsSize*binary.MaxVarintLen64)
    // nilでない場合は1バイト目に1をセット
    out[0] = 1
    // スライスの長さをセット
    n += binary.PutUvarint(out[n:], uint64(intsSize))
    // スライスを1要素ずつセット
    for _, i := range ints {
        n += binary.PutVarint(out[n:], int64(i))
    }
    return out[:n], n
}

func SliceDecode(in []byte) ([]int, int) {
    n := 0
    // 1バイト目が0ならnilを返す
    sliceIsNotNil, sliceIsNotNilLen := binary.Uvarint(in[n:])
    n += sliceIsNotNilLen
    if sliceIsNotNil == 0 {
        return nil, n
    }
    // スライスの長さを読み取る
    sliceLen, sliceLenLen := binary.Uvarint(in[n:])
    n += sliceLenLen
    slice := make([]int, sliceLen)
    // 長さの回数だけ読み取る
    for i := 0; i < int(sliceLen); i++ {
        intRaw, intLen := binary.Varint(in[n:])
        slice[i] = int(intRaw)
        n += intLen
    }
    return slice, n
}

time.Time

MarshalBinaryUnmarshalBinary というメソッドが標準で用意されているので、これをそのまま使えます。 pkg.go.dev

任意の構造体に実装する

以上の方法で任意の構造体をエンコード・デコードします。例えば以下のような構造体があった場合、

type TestStruct struct {
    Str    string
    Bool   bool
    Int    int
    Int16  int16
    Int64  int64
    Uint   uint
    Uint8  uint8
    Uint32 uint32
    Time   time.Time
}

エンコード・デコードは以下のように実装できます。

const (
    MaxVarintLen8    = 2
    VarintLenBool    = 1
    VarintLenTime    = 15
    VarintLenPointer = 1
)

func (s TestStruct) Encode() ([]byte, error) {
    // エンコードに必要な最大サイズを計算する
    size := 0
    // Str
    size += binary.MaxVarintLen64
    size += len(s.Str)
    // Bool
    size += VarintLenBool
    // Int
    size += binary.MaxVarintLen64
    // Int16
    size += binary.MaxVarintLen16
    // Int64
    size += binary.MaxVarintLen64
    // Uint
    size += binary.MaxVarintLen64
    // Uint8
    size += MaxVarintLen8
    // Uint32
    size += binary.MaxVarintLen32
    // Time
    size += VarintLenTime

    // 最大サイズ分の大きさを確保
    out := make([]byte, size)

    n := 0
    // Str
    strSize := len(s.Str)
    n += binary.PutUvarint(out[n:], uint64(strSize))
    copy(out[n:n+strSize], s.Str)
    n += strSize
    // Bool
    if s.Bool {
        n += binary.PutUvarint(out[n:], uint64(1))
    } else {
        n += binary.PutUvarint(out[n:], uint64(0))
    }
    // Int
    n += binary.PutVarint(out[n:], int64(s.Int))
    // Int16
    n += binary.PutVarint(out[n:], int64(s.Int16))
    // Int64
    n += binary.PutVarint(out[n:], s.Int64)
    // Uint
    n += binary.PutUvarint(out[n:], uint64(s.Uint))
    // Uint8
    n += binary.PutUvarint(out[n:], uint64(s.Uint8))
    // Uint32
    n += binary.PutUvarint(out[n:], uint64(s.Uint32))
    // Time
    timeBytes, err := s.Time.MarshalBinary()
    if err != nil {
        return nil, err
    }
    copy(out[n:n+VarintLenTime], timeBytes)
    n += VarintLenTime

    return out[:n], nil
}

func (s *TestStruct) Decode(in []byte) (int, error) {
    n := 0

    // Str
    strLen, strLenLen := binary.Uvarint(in)
    n += strLenLen
    s.Str = string(in[n : n+int(strLen)])
    n += int(strLen)
    // Bool
    boolRaw, boolLen := binary.Uvarint(in[n:])
    s.Bool = boolRaw == 1
    n += boolLen
    // Int
    intRaw, intLen := binary.Varint(in[n:])
    s.Int = int(intRaw)
    n += intLen
    // Int16
    int16Raw, int16Len := binary.Varint(in[n:])
    s.Int16 = int16(int16Raw)
    n += int16Len
    // Int64
    int64Raw, int64Len := binary.Varint(in[n:])
    s.Int64 = int64(int64Raw)
    n += int64Len
    // Uint
    uintRaw, uintLen := binary.Uvarint(in[n:])
    s.Uint = uint(uintRaw)
    n += uintLen
    // Uint8
    uint8Raw, uint8Len := binary.Uvarint(in[n:])
    s.Uint8 = uint8(uint8Raw)
    n += uint8Len
    // Uint32
    uint32Raw, uint32Len := binary.Uvarint(in[n:])
    s.Uint32 = uint32(uint32Raw)
    n += uint32Len
    // Time
    err := s.Time.UnmarshalBinary(in[n : n+VarintLenTime])
    if err != nil {
        return 0, err
    }
    n += VarintLenTime
    return n, nil
}

以下のコードでテストしてみます。

 data := TestStruct{
        Str:    "test_string",
        Bool:   true,
        Int:    1,
        Int16:  10000,
        Int64:  1000000000000000000,
        Uint:   1,
        Uint8:  100,
        Uint32: 1000000000,
        Time:   time.Now(),
    }
    bytes, _ := data.Encode()
    fmt.Println(len(bytes))

    decoded := TestStruct{}
    decoded.Decode(bytes)

    if diff := cmp.Diff(data, decoded); diff != "" {
        fmt.Println(diff)
    } else {
        fmt.Println("no diff")
    }

実行すると以下の出力になり、正常にエンコード・デコードできていそうです。

48
no diff

パフォーマンス比較

以下の構造体のスライス TestStructsを使って計測します。

type TestStruct struct {
    Str        string
    Bool       bool
    Int        int
    Int16      int16
    Int64      int64
    Uint       uint
    Uint8      uint8
    Uint32     uint32
    Time       time.Time
    SubPointer *TestSubStruct
    Subs       TestSubStructs
}

type TestStructs []TestStruct

type TestSubStruct struct {
    Str    string
    Bool   bool
    Int    int
    Int16  int16
    Int64  int64
    Uint   uint
    Uint8  uint8
    Uint32 uint32
    Time   time.Time
}

type TestSubStructs []TestSubStruct

エンコード・デコードの処理はかなり長いので興味がある方は以下を見てください。

github.com

テスト用のスライスをランダムに生成し、エンコード・デコードのベンチマークを測定します。

func createTestStructs(size int) TestStructs {
    ss := make(TestStructs, size)
    for i := range ss {
        subs := make(TestSubStructs, 10)
        for j := range subs {
            subs[j] = createTestSubStruct()
        }
        sub := createTestSubStruct()
        ss[i] = TestStruct{
            Str:        randString(10),
            Bool:       rand.Int()%2 == 1,
            Int:        rand.Int(),
            Int16:      int16(rand.Int()),
            Int64:      int64(rand.Int()),
            Uint:       uint(rand.Uint64()),
            Uint8:      uint8(rand.Uint64()),
            Uint32:     uint32(rand.Uint64()),
            Time:       time.Now(),
            SubPointer: &sub,
            Subs:       subs,
        }
    }
    return ss
}

func createTestSubStruct() TestSubStruct {
    return TestSubStruct{
        Str:    randString(10),
        Bool:   rand.Int()%2 == 1,
        Int:    rand.Int(),
        Int16:  int16(rand.Int()),
        Int64:  int64(rand.Int()),
        Uint:   uint(rand.Uint64()),
        Uint8:  uint8(rand.Uint64()),
        Uint32: uint32(rand.Uint64()),
        Time:   time.Now(),
    }
}

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func randString(n int) string {
    b := make([]byte, n)
    for i := range b {
        b[i] = letters[int(rand.Int63()%int64(len(letters)))]
    }
    return string(b)
}

スライスの長さを変えて測ってみます。テストコードは以下です。

https://github.com/yutakahashi114/go-struct-encode/blob/cd6a2e73008d5542800860433df4ce4e10b80e5d/main_test.go

結果は以下になりました。

f:id:hrb-takahashi-yu:20211128171253p:plain

スライスの長さが10,000の場合、

  • エンコード速度:jsonの26倍、gobの3.7倍
  • デコード速度:jsonの20倍、gobの2.4倍

になりました。メモリ消費も0.2 ~ 0.6倍に抑えられています。

スライスの長さが1の場合、

  • エンコード速度:jsonの8.6倍、gobの9.7倍
  • デコード速度:jsonの22倍、gobの18倍

となり、メモリ消費は0.1 ~ 0.4倍です。gobは単一の構造体が苦手らしいので、その影響が出ていそうです。

zenn.dev

ついでにスライスの長さ10,000の場合のエンコード後のバイト列の長さを測ってみました。

23981425
8846815
7790238

上からjson、gob、自前です。

もう少し頑張ってみる

time.Time のエンコード時にメモリのコピーが発生しているので、これを無くせばもう少し速くなりそうです。そこで、引数で受け取ったバイト列にエンコード結果をセットできるような関数を用意しました。

var timeZero = time.Time{}.Unix()

func TimeMarshalBinary(t time.Time, out []byte) (int, error) {
    var offsetMin int16 // minutes east of UTC. -1 is UTC.

    if t.Location() == time.UTC {
        offsetMin = -1
    } else {
        _, offset := t.Zone()
        if offset%60 != 0 {
            return 0, errors.New("Time.MarshalBinary: zone offset has fractional minute")
        }
        offset /= 60
        if offset < -32768 || offset == -1 || offset > 32767 {
            return 0, errors.New("Time.MarshalBinary: unexpected zone offset")
        }
        offsetMin = int16(offset)
    }

    unix := t.Unix()
    sec := unix - timeZero
    nsec := t.UnixNano() - unix*1000000000
    out[0] = 1               // byte 0 : version
    out[1] = byte(sec >> 56) // bytes 1-8: seconds
    out[2] = byte(sec >> 48)
    out[3] = byte(sec >> 40)
    out[4] = byte(sec >> 32)
    out[5] = byte(sec >> 24)
    out[6] = byte(sec >> 16)
    out[7] = byte(sec >> 8)
    out[8] = byte(sec)
    out[9] = byte(nsec >> 24) // bytes 9-12: nanoseconds
    out[10] = byte(nsec >> 16)
    out[11] = byte(nsec >> 8)
    out[12] = byte(nsec)
    out[13] = byte(offsetMin >> 8) // bytes 13-14: zone offset in minutes
    out[14] = byte(offsetMin)

    return VarintLenTime, nil
}

既存の Time.MarshalBinary をコピペして少しいじっただけです。time.Time のエンコードをこれに差し替えました。

https://github.com/yutakahashi114/go-struct-encode/blob/cd6a2e73008d5542800860433df4ce4e10b80e5d/test_struct.go#L283

結果は以下になります。

f:id:hrb-takahashi-yu:20211128172702p:plain

allocateがなくなった分、1.2倍ほど速くなりました。長さ10,000の場合、jsonの31倍、gobの4.4倍です。

2021/12/04追記

「Protocol Buffers と比較したらどうなるの?」っと聞かれたので試してみました。

その時におすすめされた以下を使います。速いらしいです。

github.com

任意の構造体のエンコードはできないので、テストに使った構造体と同じようなprotoファイルを用意しました。

syntax = "proto3";

package proto;

import "google/protobuf/timestamp.proto";

message TestStructs {
  repeated TestStruct ss = 1;
}

message TestStruct {
  string                    str         = 1;
  bool                      bool        = 2;
  int64                     int         = 3;
  int32                     int16       = 4;
  int64                     int64       = 5;
  uint64                    uint        = 6;
  uint32                    uint8       = 7;
  uint32                    uint32      = 8;
  google.protobuf.Timestamp time        = 9;
  TestSubStruct             sub_pointer = 10;
  repeated TestSubStruct    subs        = 11;
}

message TestSubStruct {
  string                    str    = 1;
  bool                      bool   = 2;
  int64                     int    = 3;
  int32                     int16  = 4;
  int64                     int64  = 5;
  uint64                    uint   = 6;
  uint32                    uint8  = 7;
  uint32                    uint32 = 8;
  google.protobuf.Timestamp time   = 9;
}

Goのコードを生成します。

# protoc -I=. -I=$GOPATH/src -I=$GOPATH/src/github.com/gogo/protobuf/protobuf --gogofaster_out=Mgoogle/protobuf/timestamp.proto=github.com/gogo/protobuf/types:. struct.proto

TestStructs から proto.TestStructs へ変換する関数を用意し、proto.TestStructs のエンコード・デコードのベンチマークをとります。(「任意の構造体」ではないので参考値ですが)

結果は以下になりました。

f:id:hrb-takahashi-yu:20211204154634p:plain

めっちゃ速いです。エンコードは自前とほぼ同じです。デコードはそこまでですが、それでもgobより速いです。自動生成でここまで速いとはすごいですね。

まとめ

頑張ったらエンコードはgobの4.4倍、デコードはgobの2.4倍の速度になりました。逆に頑張ってもそれくらいの速度改善にしかならないので、よほどエンコード・デコードのパフォーマンスで困っていない限りgobで良いな、と思いました。もっと単純な構造体なら実装も楽なので採用しても良いかもしれません。

もっと速くなる案があれば教えていただきたいです。

最後にベンチマークの結果をそのまま載せておきます。

# go test -benchmem -bench=. .
goos: linux
goarch: amd64
pkg: encode
cpu: Intel(R) Core(TM) i7-8557U CPU @ 1.70GHz
Benchmark_encode_____json_____1-4          82219             14040 ns/op            3288 B/op         14 allocs/op
Benchmark_encode______gob_____1-4          76011             15790 ns/op            7296 B/op         93 allocs/op
Benchmark_encode_____self_____1-4         772065              1633 ns/op            1216 B/op         13 allocs/op
Benchmark_encode_selftime_____1-4         945372              1314 ns/op            1024 B/op          1 allocs/op
Benchmark_encode_____json____10-4           8667            135845 ns/op           30371 B/op        122 allocs/op
Benchmark_encode______gob____10-4          15444             89294 ns/op           53728 B/op        317 allocs/op
Benchmark_encode_____self____10-4          72669             16511 ns/op           11392 B/op        121 allocs/op
Benchmark_encode_selftime____10-4          89332             13599 ns/op            9472 B/op          1 allocs/op
Benchmark_encode_____json___100-4            870           1442269 ns/op          303406 B/op       1202 allocs/op
Benchmark_encode______gob___100-4           1879            624703 ns/op          523552 B/op       2485 allocs/op
Benchmark_encode_____self___100-4           7363            165785 ns/op          117504 B/op       1201 allocs/op
Benchmark_encode_selftime___100-4           8685            164211 ns/op           98304 B/op          1 allocs/op
Benchmark_encode_____json__1000-4             75          14407019 ns/op         2976444 B/op      12002 allocs/op
Benchmark_encode______gob__1000-4            181           7098993 ns/op         6026404 B/op      24095 allocs/op
Benchmark_encode_____self__1000-4            633           1835921 ns/op         1117696 B/op      12001 allocs/op
Benchmark_encode_selftime__1000-4            790           1509317 ns/op          925696 B/op          1 allocs/op
Benchmark_encode_____json_10000-4              7         459676600 ns/op        40510650 B/op     120008 allocs/op
Benchmark_encode______gob_10000-4             16          65469900 ns/op        58843049 B/op     240105 allocs/op
Benchmark_encode_____self_10000-4             58          17858890 ns/op        11160576 B/op     120001 allocs/op
Benchmark_encode_selftime_10000-4             72          14867447 ns/op         9240576 B/op          1 allocs/op
Benchmark_decode_____json_____1-4          24745             48605 ns/op            5632 B/op        115 allocs/op
Benchmark_decode______gob_____1-4          30276             39866 ns/op           13637 B/op        348 allocs/op
Benchmark_decode_____self_____1-4         585103              2236 ns/op            1312 B/op         15 allocs/op
Benchmark_decode_selftime_____1-4         588283              2233 ns/op            1312 B/op         15 allocs/op
Benchmark_decode_____json____10-4           2521            482171 ns/op           52616 B/op       1063 allocs/op
Benchmark_decode______gob____10-4          12969             94039 ns/op           34814 B/op        492 allocs/op
Benchmark_decode_____self____10-4          48012             23574 ns/op           13120 B/op        141 allocs/op
Benchmark_decode_selftime____10-4          46819             22666 ns/op           13120 B/op        141 allocs/op
Benchmark_decode_____json___100-4            254           4856967 ns/op          534768 B/op      10490 allocs/op
Benchmark_decode______gob___100-4           2083            581402 ns/op          240940 B/op       1932 allocs/op
Benchmark_decode_____self___100-4           5622            235138 ns/op          130688 B/op       1401 allocs/op
Benchmark_decode_selftime___100-4           5599            234787 ns/op          130688 B/op       1401 allocs/op
Benchmark_decode_____json__1000-4             24          47391775 ns/op         5201429 B/op     104563 allocs/op
Benchmark_decode______gob__1000-4            201           5501483 ns/op         2290962 B/op      16332 allocs/op
Benchmark_decode_____self__1000-4            520           2327776 ns/op         1306881 B/op      14001 allocs/op
Benchmark_decode_selftime__1000-4            514           2337885 ns/op         1306881 B/op      14001 allocs/op
Benchmark_decode_____json_10000-4              3         468327567 ns/op        52413248 B/op    1045349 allocs/op
Benchmark_decode______gob_10000-4             20          56158780 ns/op        22782913 B/op     160332 allocs/op
Benchmark_decode_____self_10000-4             48          23602694 ns/op        13044240 B/op     140001 allocs/op
Benchmark_decode_selftime_10000-4             46          22891991 ns/op        13044240 B/op     140001 allocs/op

2021/12/04追記

Protocol Buffers を追加して測り直した分です。

Benchmark_encode_____json_____1-4          80320             14865 ns/op            3288 B/op         14 allocs/op
Benchmark_encode______gob_____1-4          62854             16038 ns/op            7296 B/op         93 allocs/op
Benchmark_encode_____self_____1-4         754052              1651 ns/op            1216 B/op         13 allocs/op
Benchmark_encode_selftime_____1-4         916267              1369 ns/op            1024 B/op          1 allocs/op
Benchmark_encode_protobuf_____1-4         803013              1607 ns/op             912 B/op          2 allocs/op
Benchmark_encode_____json____10-4           8752            141980 ns/op           30370 B/op        122 allocs/op
Benchmark_encode______gob____10-4          17295             74739 ns/op           53728 B/op        317 allocs/op
Benchmark_encode_____self____10-4          63856             17466 ns/op           11392 B/op        121 allocs/op
Benchmark_encode_selftime____10-4          83157             13136 ns/op            9472 B/op          1 allocs/op
Benchmark_encode_protobuf____10-4          96116             16727 ns/op            9488 B/op          2 allocs/op
Benchmark_encode_____json___100-4            823           1392776 ns/op          304117 B/op       1202 allocs/op
Benchmark_encode______gob___100-4           1957            673258 ns/op          523552 B/op       2485 allocs/op
Benchmark_encode_____self___100-4           6825            201729 ns/op          117504 B/op       1201 allocs/op
Benchmark_encode_selftime___100-4           6709            156873 ns/op           98304 B/op          1 allocs/op
Benchmark_encode_protobuf___100-4           8925            133352 ns/op           90128 B/op          2 allocs/op
Benchmark_encode_____json__1000-4             76          14612655 ns/op         3100622 B/op      12002 allocs/op
Benchmark_encode______gob__1000-4            171           6456594 ns/op         6026403 B/op      24095 allocs/op
Benchmark_encode_____self__1000-4            688           2182507 ns/op         1117696 B/op      12001 allocs/op
Benchmark_encode_selftime__1000-4            632           1678611 ns/op          925696 B/op          1 allocs/op
Benchmark_encode_protobuf__1000-4            667           1515424 ns/op          901136 B/op          2 allocs/op
Benchmark_encode_____json_10000-4              7         155124814 ns/op        29746667 B/op     120004 allocs/op
Benchmark_encode______gob_10000-4             18          64874194 ns/op        58843053 B/op     240105 allocs/op
Benchmark_encode_____self_10000-4             69          18463733 ns/op        11160578 B/op     120001 allocs/op
Benchmark_encode_selftime_10000-4             73          14718711 ns/op         9240576 B/op          1 allocs/op
Benchmark_encode_protobuf_10000-4             70          16400381 ns/op         9003027 B/op          2 allocs/op
Benchmark_decode_____json_____1-4          24993             48118 ns/op            5632 B/op        115 allocs/op
Benchmark_decode______gob_____1-4          27406             38389 ns/op           13637 B/op        348 allocs/op
Benchmark_decode_____self_____1-4         580430              2222 ns/op            1312 B/op         15 allocs/op
Benchmark_decode_selftime_____1-4         591904              2192 ns/op            1312 B/op         15 allocs/op
Benchmark_decode_protobuf_____1-4         316608              3711 ns/op            2056 B/op         44 allocs/op
Benchmark_decode_____json____10-4           2572            484292 ns/op           52616 B/op       1063 allocs/op
Benchmark_decode______gob____10-4          13778             91646 ns/op           34813 B/op        492 allocs/op
Benchmark_decode_____self____10-4          53476             22444 ns/op           13120 B/op        141 allocs/op
Benchmark_decode_selftime____10-4          55375             23805 ns/op           13120 B/op        141 allocs/op
Benchmark_decode_protobuf____10-4          31502             36918 ns/op           20368 B/op        417 allocs/op
Benchmark_decode_____json___100-4            256           4851948 ns/op          534768 B/op      10490 allocs/op
Benchmark_decode______gob___100-4           2065            580356 ns/op          240942 B/op       1932 allocs/op
Benchmark_decode_____self___100-4           5539            239239 ns/op          130688 B/op       1401 allocs/op
Benchmark_decode_selftime___100-4           5594            228990 ns/op          130688 B/op       1401 allocs/op
Benchmark_decode_protobuf___100-4           3372            380151 ns/op          202880 B/op       4110 allocs/op
Benchmark_decode_____json__1000-4             24          46784458 ns/op         5201424 B/op     104563 allocs/op
Benchmark_decode______gob__1000-4            213           5805051 ns/op         2290960 B/op      16332 allocs/op
Benchmark_decode_____self__1000-4            498           2336990 ns/op         1306880 B/op      14001 allocs/op
Benchmark_decode_selftime__1000-4            447           2820632 ns/op         1306880 B/op      14001 allocs/op
Benchmark_decode_protobuf__1000-4            272           4246900 ns/op         2024416 B/op      41013 allocs/op
Benchmark_decode_____json_10000-4              2         666970100 ns/op        52413264 B/op    1045349 allocs/op
Benchmark_decode______gob_10000-4             19          61811889 ns/op        22782912 B/op     160332 allocs/op
Benchmark_decode_____self_10000-4             45          27595242 ns/op        13044226 B/op     140001 allocs/op
Benchmark_decode_selftime_10000-4             45          31214431 ns/op        13044224 B/op     140001 allocs/op
Benchmark_decode_protobuf_10000-4             30          40762980 ns/op        20466339 B/op     410022 allocs/op