]> Cypherpunks repositories - gostls13.git/commitdiff
gob: don't allocate a slice if there's room to decode already
authorRob Pike <r@golang.org>
Mon, 19 Sep 2011 23:55:08 +0000 (16:55 -0700)
committerRob Pike <r@golang.org>
Mon, 19 Sep 2011 23:55:08 +0000 (16:55 -0700)
Fixes #2275.

R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5082041

src/pkg/gob/codec_test.go
src/pkg/gob/decode.go
src/pkg/gob/doc.go
src/pkg/gob/encoder_test.go

index a5fb91cda7841988382c20ebe4d5f8c525d777a0..2bcbf82a3093c8b85ad00a0cb019ef8b9adf3772 100644 (file)
@@ -544,7 +544,7 @@ func TestScalarDecInstructions(t *testing.T) {
                var data struct {
                        a []byte
                }
-               instr := &decInstr{decUint8Array, 6, 0, 0, ovfl}
+               instr := &decInstr{decUint8Slice, 6, 0, 0, ovfl}
                state := newDecodeStateFromData(bytesResult)
                execDec("bytes", instr, state, t, unsafe.Pointer(&data))
                if string(data.a) != "hello" {
index bf7cb95f22ca9102e55fd19ba82058cfbee65eab..9bbe1286e041528bde6a9b969f3362d361278dc3 100644 (file)
@@ -385,19 +385,29 @@ func decComplex128(i *decInstr, state *decoderState, p unsafe.Pointer) {
        *(*complex128)(p) = complex(real, imag)
 }
 
-// decUint8Array decodes byte array and stores through p a slice header
+// decUint8Slice decodes a byte slice and stores through p a slice header
 // describing the data.
-// uint8 arrays are encoded as an unsigned count followed by the raw bytes.
-func decUint8Array(i *decInstr, state *decoderState, p unsafe.Pointer) {
+// uint8 slices are encoded as an unsigned count followed by the raw bytes.
+func decUint8Slice(i *decInstr, state *decoderState, p unsafe.Pointer) {
        if i.indir > 0 {
                if *(*unsafe.Pointer)(p) == nil {
                        *(*unsafe.Pointer)(p) = unsafe.Pointer(new([]uint8))
                }
                p = *(*unsafe.Pointer)(p)
        }
-       b := make([]uint8, state.decodeUint())
-       state.b.Read(b)
-       *(*[]uint8)(p) = b
+       n := int(state.decodeUint())
+       if n < 0 {
+               errorf("negative length decoding []byte")
+       }
+       slice := (*[]uint8)(p)
+       if cap(*slice) < n {
+               *slice = make([]uint8, n)
+       } else {
+               *slice = (*slice)[0:n]
+       }
+       if _, err := state.b.Read(*slice); err != nil {
+               errorf("error decoding []byte: %s", err)
+       }
 }
 
 // decString decodes byte array and stores through p a string header
@@ -653,12 +663,15 @@ func (dec *Decoder) decodeSlice(atyp reflect.Type, state *decoderState, p uintpt
                }
                p = *(*uintptr)(up)
        }
-       // Allocate storage for the slice elements, that is, the underlying array.
+       // Allocate storage for the slice elements, that is, the underlying array,
+       // if the existing slice does not have the capacity.
        // Always write a header at p.
        hdrp := (*reflect.SliceHeader)(unsafe.Pointer(p))
-       hdrp.Data = uintptr(unsafe.NewArray(atyp.Elem(), n))
+       if hdrp.Cap < n {
+               hdrp.Data = uintptr(unsafe.NewArray(atyp.Elem(), n))
+               hdrp.Cap = n
+       }
        hdrp.Len = n
-       hdrp.Cap = n
        dec.decodeArrayHelper(state, hdrp.Data, elemOp, elemWid, n, elemIndir, ovfl)
 }
 
@@ -842,7 +855,7 @@ func (dec *Decoder) decOpFor(wireId typeId, rt reflect.Type, name string, inProg
                case reflect.Slice:
                        name = "element of " + name
                        if t.Elem().Kind() == reflect.Uint8 {
-                               op = decUint8Array
+                               op = decUint8Slice
                                break
                        }
                        var elemId typeId
index a9284ced7f97743795b61760ee79e32bc4444652..05ebef195932775e2e99962ef38cc09d05fe4900 100644 (file)
@@ -68,7 +68,10 @@ the destination variable must be able to represent the value or the decode
 operation will fail.
 
 Structs, arrays and slices are also supported.  Strings and arrays of bytes are
-supported with a special, efficient representation (see below).
+supported with a special, efficient representation (see below).  When a slice is
+decoded, if the existing slice has capacity the slice will be extended in place;
+if not, a new array is allocated.  Regardless, the length of the resuling slice
+reports the number of elements decoded.
 
 Functions and channels cannot be sent in a gob.  Attempting
 to encode a value that contains one will fail.
index f5ee423cb2b6e5abc7e2302f3ff29e0f44a460ad..79d28970100d7cfc6f3837331fa68d5c53b45915 100644 (file)
@@ -575,6 +575,56 @@ func TestGobMapInterfaceEncode(t *testing.T) {
        enc := NewEncoder(buf)
        err := enc.Encode(m)
        if err != nil {
-               t.Errorf("gob.Encode map: %s", err)
+               t.Errorf("encode map: %s", err)
+       }
+}
+
+func TestSliceReusesMemory(t *testing.T) {
+       buf := bytes.NewBuffer(nil)
+       // Bytes
+       {
+               x := []byte("abcd")
+               enc := NewEncoder(buf)
+               err := enc.Encode(x)
+               if err != nil {
+                       t.Errorf("bytes: encode: %s", err)
+               }
+               // Decode into y, which is big enough.
+               y := []byte("ABCDE")
+               addr := &y[0]
+               dec := NewDecoder(buf)
+               err = dec.Decode(&y)
+               if err != nil {
+                       t.Fatal("bytes: decode:", err)
+               }
+               if !bytes.Equal(x, y) {
+                       t.Errorf("bytes: expected %q got %q\n", x, y)
+               }
+               if addr != &y[0] {
+                       t.Errorf("bytes: unnecessary reallocation")
+               }
+       }
+       // general slice
+       {
+               x := []int("abcd")
+               enc := NewEncoder(buf)
+               err := enc.Encode(x)
+               if err != nil {
+                       t.Errorf("ints: encode: %s", err)
+               }
+               // Decode into y, which is big enough.
+               y := []int("ABCDE")
+               addr := &y[0]
+               dec := NewDecoder(buf)
+               err = dec.Decode(&y)
+               if err != nil {
+                       t.Fatal("ints: decode:", err)
+               }
+               if !reflect.DeepEqual(x, y) {
+                       t.Errorf("ints: expected %q got %q\n", x, y)
+               }
+               if addr != &y[0] {
+                       t.Errorf("ints: unnecessary reallocation")
+               }
        }
 }