Gob で DeepCopy

Apr 6, 2023 23:45 · 250 words · 2 minute read

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実装が必要。