]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: use zero buffer to back the Value returned by Zero
authorKeith Randall <khr@golang.org>
Fri, 18 Sep 2020 21:19:22 +0000 (14:19 -0700)
committerKeith Randall <khr@golang.org>
Fri, 18 Sep 2020 21:47:28 +0000 (21:47 +0000)
In the common case (<1KB types), no allocation is required
by reflect.Zero.

Also use memclr instead of memmove in Set when the source
is known to be zero.

Fixes #33136

Change-Id: Ic66871930fbb53328032e587153ebd12995ccf55
Reviewed-on: https://go-review.googlesource.com/c/go/+/192331
Trust: Keith Randall <khr@golang.org>
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Martin Möhrmann <moehrmann@google.com>
src/reflect/all_test.go
src/reflect/value.go
src/runtime/map.go

index 5a12699472197c347a0a52addf643f4c72f83be2..abdfe4190841cbc4743bcabb74f1039bb6e3c5a4 100644 (file)
@@ -6006,6 +6006,14 @@ func TestReflectMethodTraceback(t *testing.T) {
        }
 }
 
+func TestSmallZero(t *testing.T) {
+       type T [10]byte
+       typ := TypeOf(T{})
+       if allocs := testing.AllocsPerRun(100, func() { Zero(typ) }); allocs > 0 {
+               t.Errorf("Creating small zero values caused %f allocs, want 0", allocs)
+       }
+}
+
 func TestBigZero(t *testing.T) {
        const size = 1 << 10
        var v [size]byte
@@ -6017,6 +6025,27 @@ func TestBigZero(t *testing.T) {
        }
 }
 
+func TestZeroSet(t *testing.T) {
+       type T [16]byte
+       type S struct {
+               a uint64
+               T T
+               b uint64
+       }
+       v := S{
+               a: 0xaaaaaaaaaaaaaaaa,
+               T: T{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9},
+               b: 0xbbbbbbbbbbbbbbbb,
+       }
+       ValueOf(&v).Elem().Field(1).Set(Zero(TypeOf(T{})))
+       if v != (S{
+               a: 0xaaaaaaaaaaaaaaaa,
+               b: 0xbbbbbbbbbbbbbbbb,
+       }) {
+               t.Fatalf("Setting a field to a Zero value didn't work")
+       }
+}
+
 func TestFieldByIndexNil(t *testing.T) {
        type P struct {
                F int
index c6f24a5609500ac793d9b96466747fe9b13d1b27..a14131e1f8344229219204fc5693017f428132df 100644 (file)
@@ -1553,7 +1553,11 @@ func (v Value) Set(x Value) {
        }
        x = x.assignTo("reflect.Set", v.typ, target)
        if x.flag&flagIndir != 0 {
-               typedmemmove(v.typ, v.ptr, x.ptr)
+               if x.ptr == unsafe.Pointer(&zeroVal[0]) {
+                       typedmemclr(v.typ, v.ptr)
+               } else {
+                       typedmemmove(v.typ, v.ptr, x.ptr)
+               }
        } else {
                *(*unsafe.Pointer)(v.ptr) = x.ptr
        }
@@ -2360,11 +2364,23 @@ func Zero(typ Type) Value {
        t := typ.(*rtype)
        fl := flag(t.Kind())
        if ifaceIndir(t) {
-               return Value{t, unsafe_New(t), fl | flagIndir}
+               var p unsafe.Pointer
+               if t.size <= maxZero {
+                       p = unsafe.Pointer(&zeroVal[0])
+               } else {
+                       p = unsafe_New(t)
+               }
+               return Value{t, p, fl | flagIndir}
        }
        return Value{t, nil, fl}
 }
 
+// must match declarations in runtime/map.go.
+const maxZero = 1024
+
+//go:linkname zeroVal runtime.zeroVal
+var zeroVal [maxZero]byte
+
 // New returns a Value representing a pointer to a new zero value
 // for the specified type. That is, the returned Value's Type is PtrTo(typ).
 func New(typ Type) Value {
index 8be1d3991d97ce26512d73cebf15909b9410b423..6f31f23d6fba33fdf56901a71b2ea924758777f8 100644 (file)
@@ -1380,5 +1380,5 @@ func reflectlite_maplen(h *hmap) int {
        return h.count
 }
 
-const maxZero = 1024 // must match value in cmd/compile/internal/gc/walk.go:zeroValSize
+const maxZero = 1024 // must match value in reflect/value.go:maxZero cmd/compile/internal/gc/walk.go:zeroValSize
 var zeroVal [maxZero]byte