GoでDeepCopyをGOBで実装する上での考察 🔗
Go のプラクティスとして、DeepCopy する場合は encoding/json を用いてシリアライズ => デシリアライズする方法がある。
これを Gob に変えるとどれくらい変わるのか、の実験。
ベンチマークをとってみた 🔗
package main
import (
"bytes"
"encoding/gob"
"encoding/json"
"log"
"testing"
)
func Benchmark(b *testing.B) {
h := "hoge"
f := "fuga"
br := bar{
C: true,
d: &f,
}
a := Foo{
A: 1,
B: &h,
X: &br,
}
b.Run("gob: global codec", func(b *testing.B) {
enc, dec := upGOB()
for i := 0; i < b.N; i++ {
cloneByGOB(enc, dec, a)
}
})
b.Run("gob: local codec", func(b *testing.B) {
for i := 0; i < b.N; i++ {
enc, dec := upGOB()
cloneByGOB(enc, dec, a)
}
})
b.Run("json", func(b *testing.B) {
for i := 0; i < b.N; i++ {
cloneByJSON(a)
}
})
}
type Foo struct {
A int
B *string
X *bar
}
type bar struct {
C bool
d *string
}
func upGOB() (*gob.Encoder, *gob.Decoder) {
var buf bytes.Buffer
return gob.NewEncoder(&buf), gob.NewDecoder(&buf)
}
func cloneByGOB(enc *gob.Encoder, dec *gob.Decoder, v Foo) {
if err := enc.Encode(v); err != nil {
log.Fatal(err)
}
c := Foo{}
if err := dec.Decode(&c); err != nil {
log.Fatal(err)
}
}
func cloneByJSON(v Foo) {
buf, err := json.Marshal(v)
if err != nil {
log.Fatal(err)
}
c := Foo{}
if err := json.Unmarshal(buf, &c); err != nil {
log.Fatal(err)
}
}
goos: darwin
goarch: arm64
pkg: sample
Benchmark/gob:_global_codec-8 2699797 437.4 ns/op 84 B/op 5 allocs/op
Benchmark/gob:_local_codec-8 102644 11952 ns/op 8796 B/op 227 allocs/op
Benchmark/json-8 1350080 867.4 ns/op 400 B/op 12 allocs/op
PASS
ok sample 5.351s
- json に比べて、スピードは2倍くらい、メモリ効率は5倍くらい。
- doc にもあるように、毎回 encoder をアロケートするような使い方はよろしくない(それはそう)。
- encoding/jsonと同じく、privateなフィールドを出力したい場合は独自のEncoder実装が必要。