reflect: add Value.SetZero

The v.SetZero method is a faster equivalent of v.Set(Zero(v.Type())).

Performance:

	                     Direct         CachedZero     NewZero
	SetZero/Bool         2.20ns ± 0%    8.97ns ± 5%    11.4ns ± 1%
	SetZero/Int          3.08ns ± 1%    9.06ns ± 1%    11.5ns ± 0%
	SetZero/Uint         2.88ns ± 1%    9.04ns ± 1%    11.7ns ± 5%
	SetZero/Float        2.65ns ± 2%    9.05ns ± 1%    11.5ns ± 1%
	SetZero/Complex      2.68ns ± 3%    9.31ns ± 1%    11.7ns ± 1%
	SetZero/Array        6.69ns ± 4%    9.32ns ± 1%    11.8ns ± 1%
	SetZero/Chan         3.31ns ± 1%    6.19ns ± 1%    8.20ns ± 1%
	SetZero/Func         3.32ns ± 1%    6.20ns ± 0%    8.24ns ± 1%
	SetZero/Interface    2.66ns ± 1%    9.31ns ± 1%    11.8ns ± 1%
	SetZero/Map          3.31ns ± 1%    6.21ns ± 2%    8.19ns ± 1%
	SetZero/Pointer      3.30ns ± 1%    6.22ns ± 1%    8.17ns ± 1%
	SetZero/Slice        2.90ns ± 4%    9.13ns ± 1%    11.6ns ± 1%
	SetZero/String       3.11ns ± 1%    9.30ns ± 1%    11.8ns ± 2%
	SetZero/Struct       6.37ns ± 1%    9.18ns ± 4%    11.5ns ± 1%

where:

	* Direct is measuring Value.SetZero
	* CachedZero is measuring Value.Set with a cached Zero value
	* NewZero is measuring Value.Set with a new Zero value

Fixes #52376

Change-Id: I793ca723aa97627824c5f5b356b2da30c8e46d71
Reviewed-on: https://go-review.googlesource.com/c/go/+/411476
Auto-Submit: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Dan Kortschak <dan@kortschak.io>
This commit is contained in:
Joe Tsai 2022-06-09 18:20:47 -07:00 committed by Gopher Robot
parent e0ec1d6770
commit bc1d0d8eb1
3 changed files with 108 additions and 2 deletions

1
api/next/52376.txt Normal file
View file

@ -0,0 +1 @@
pkg reflect, method (Value) SetZero() #52376

View file

@ -1374,6 +1374,8 @@ func TestIsZero(t *testing.T) {
{[3][]int{{1}}, false}, // incomparable array
{[1 << 12]byte{}, true},
{[1 << 12]byte{1}, false},
{[3]Value{}, true},
{[3]Value{{}, ValueOf(0), {}}, false},
// Chan
{(chan string)(nil), true},
{make(chan string), false},
@ -1406,6 +1408,8 @@ func TestIsZero(t *testing.T) {
{struct{ p *int }{new(int)}, false}, // direct pointer struct
{struct{ s []int }{}, true}, // incomparable struct
{struct{ s []int }{[]int{1}}, false}, // incomparable struct
{struct{ Value }{}, true},
{struct{ Value }{ValueOf(0)}, false},
// UnsafePointer
{(unsafe.Pointer)(nil), true},
{(unsafe.Pointer)(new(int)), false},
@ -1425,6 +1429,13 @@ func TestIsZero(t *testing.T) {
if !Zero(TypeOf(tt.x)).IsZero() {
t.Errorf("%d: IsZero(Zero(TypeOf((%s)(%+v)))) is false", i, x.Kind(), tt.x)
}
p := New(x.Type()).Elem()
p.Set(x)
p.SetZero()
if !p.IsZero() {
t.Errorf("%d: IsZero((%s)(%+v)) is true after SetZero", i, p.Kind(), tt.x)
}
}
func() {
@ -1456,6 +1467,46 @@ func BenchmarkIsZero(b *testing.B) {
}
}
func BenchmarkSetZero(b *testing.B) {
source := ValueOf(new(struct {
Bool bool
Int int64
Uint uint64
Float float64
Complex complex128
Array [4]Value
Chan chan Value
Func func() Value
Interface interface{ String() string }
Map map[string]Value
Pointer *Value
Slice []Value
String string
Struct Value
})).Elem()
for i := 0; i < source.NumField(); i++ {
name := source.Type().Field(i).Name
value := source.Field(i)
zero := Zero(value.Type())
b.Run(name+"/Direct", func(b *testing.B) {
for i := 0; i < b.N; i++ {
value.SetZero()
}
})
b.Run(name+"/CachedZero", func(b *testing.B) {
for i := 0; i < b.N; i++ {
value.Set(zero)
}
})
b.Run(name+"/NewZero", func(b *testing.B) {
for i := 0; i < b.N; i++ {
value.Set(Zero(value.Type()))
}
})
}
}
func TestInterfaceExtraction(t *testing.T) {
var s struct {
W io.Writer

View file

@ -1615,12 +1615,66 @@ func (v Value) IsZero() bool {
}
return true
default:
// This should never happens, but will act as a safeguard for
// later, as a default value doesn't makes sense here.
// This should never happen, but will act as a safeguard for later,
// as a default value doesn't makes sense here.
panic(&ValueError{"reflect.Value.IsZero", v.Kind()})
}
}
// SetZero sets v to be the zero value of v's type.
// It panics if CanSet returns false.
func (v Value) SetZero() {
v.mustBeAssignable()
switch v.kind() {
case Bool:
*(*bool)(v.ptr) = false
case Int:
*(*int)(v.ptr) = 0
case Int8:
*(*int8)(v.ptr) = 0
case Int16:
*(*int16)(v.ptr) = 0
case Int32:
*(*int32)(v.ptr) = 0
case Int64:
*(*int64)(v.ptr) = 0
case Uint:
*(*uint)(v.ptr) = 0
case Uint8:
*(*uint8)(v.ptr) = 0
case Uint16:
*(*uint16)(v.ptr) = 0
case Uint32:
*(*uint32)(v.ptr) = 0
case Uint64:
*(*uint64)(v.ptr) = 0
case Uintptr:
*(*uintptr)(v.ptr) = 0
case Float32:
*(*float32)(v.ptr) = 0
case Float64:
*(*float64)(v.ptr) = 0
case Complex64:
*(*complex64)(v.ptr) = 0
case Complex128:
*(*complex128)(v.ptr) = 0
case String:
*(*string)(v.ptr) = ""
case Slice:
*(*unsafeheader.Slice)(v.ptr) = unsafeheader.Slice{}
case Interface:
*(*[2]unsafe.Pointer)(v.ptr) = [2]unsafe.Pointer{}
case Chan, Func, Map, Pointer, UnsafePointer:
*(*unsafe.Pointer)(v.ptr) = nil
case Array, Struct:
typedmemclr(v.typ, v.ptr)
default:
// This should never happen, but will act as a safeguard for later,
// as a default value doesn't makes sense here.
panic(&ValueError{"reflect.Value.SetZero", v.Kind()})
}
}
// Kind returns v's Kind.
// If v is the zero Value (IsValid returns false), Kind returns Invalid.
func (v Value) Kind() Kind {