diff --git a/pkg/conversion/deep_copy.go b/pkg/conversion/deep_copy.go index ba73f068f69..2fffc7f4335 100644 --- a/pkg/conversion/deep_copy.go +++ b/pkg/conversion/deep_copy.go @@ -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 +} diff --git a/pkg/conversion/deep_copy_test.go b/pkg/conversion/deep_copy_test.go index f5d545c6cef..801fbad7d48 100644 --- a/pkg/conversion/deep_copy_test.go +++ b/pkg/conversion/deep_copy_test.go @@ -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 +}