Reuse gob.Encoder and Decoder in DeepCopy

This commit is contained in:
Clayton Coleman 2015-04-11 19:51:44 -04:00
parent d577db9987
commit f95cc2b8f2
2 changed files with 72 additions and 12 deletions

View File

@ -20,24 +20,49 @@ import (
"bytes"
"encoding/gob"
"reflect"
"sync"
)
// DeepCopy makes a deep copy of source. Won't work for any private fields!
// For nil slices, will return 0-length slices. These are equivilent in
// basically every way except for the way that reflect.DeepEqual checks.
// pool is a pool of copiers
var pool = sync.Pool{
New: func() interface{} { return newGobCopier() },
}
// DeepCopy makes a deep copy of source or returns an error.
func DeepCopy(source interface{}) (interface{}, error) {
v := reflect.New(reflect.TypeOf(source))
buff := &bytes.Buffer{}
enc := gob.NewEncoder(buff)
dec := gob.NewDecoder(buff)
err := enc.Encode(source)
if err != nil {
return nil, err
}
err = dec.Decode(v.Interface())
if err != nil {
c := pool.Get().(gobCopier)
defer pool.Put(c)
if err := c.CopyInto(v.Interface(), source); err != nil {
return nil, err
}
return v.Elem().Interface(), nil
}
// gobCopier provides a copy mechanism for objects using Gob.
// This object is not safe for multiple threads because buffer
// is shared.
type gobCopier struct {
enc *gob.Encoder
dec *gob.Decoder
}
func newGobCopier() gobCopier {
buf := &bytes.Buffer{}
return gobCopier{
enc: gob.NewEncoder(buf),
dec: gob.NewDecoder(buf),
}
}
func (c *gobCopier) CopyInto(dst, src interface{}) error {
if err := c.enc.Encode(src); err != nil {
return err
}
if err := c.dec.Decode(dst); err != nil {
return err
}
return nil
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package conversion
import (
"math/rand"
"reflect"
"testing"
@ -106,3 +107,37 @@ func TestDeepCopyPointerSeparate(t *testing.T) {
t.Errorf("deep copy wasn't deep: %#q %#q", x, y)
}
}
var result interface{}
func BenchmarkDeepCopy(b *testing.B) {
table := []interface{}{
map[string]string{},
int(5),
"hello world",
struct {
A, B, C struct {
D map[string]int
}
X []int
Y []byte
}{},
}
f := fuzz.New().RandSource(rand.NewSource(1)).NilChance(.5).NumElements(0, 100)
for i := range table {
out := table[i]
obj := reflect.New(reflect.TypeOf(out)).Interface()
f.Fuzz(obj)
table[i] = obj
}
b.ResetTimer()
var r interface{}
for i := 0; i < b.N; i++ {
for j := range table {
r, _ = DeepCopy(table[j])
}
}
result = r
}