From 1ab6b790be3de2364edc3c15741afe3705f7c358 Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Thu, 9 Jun 2022 18:32:53 -0700 Subject: [PATCH] reflect: optimize Value.IsZero MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit If a struct or array is comparable, then we can leverage rtype.equal, which is almost always faster than what Go reflection can achieve. As a secondary optimization, pre-compute Value.Len and Value.NumField outside of the loop conditional. Performance: name old time/op new time/op delta IsZero/ArrayComparable 136ns ± 4% 16ns ± 1% -88.28% (p=0.008 n=5+5) IsZero/ArrayIncomparable 197ns ±10% 123ns ± 1% -37.74% (p=0.008 n=5+5) IsZero/StructComparable 26.4ns ± 0% 9.6ns ± 1% -63.68% (p=0.016 n=4+5) IsZero/StructIncomparable 43.5ns ± 1% 27.8ns ± 1% -36.21% (p=0.008 n=5+5) The incomparable types gain a performance boost since they are generally constructed from nested comparable types. Change-Id: If2c1929f8bb1b5b19306ef0c69f3c95a27d4b60d Reviewed-on: https://go-review.googlesource.com/c/go/+/411478 Reviewed-by: Dan Kortschak Run-TryBot: Joseph Tsai Reviewed-by: Than McIntosh Reviewed-by: Keith Randall Reviewed-by: Dmitri Shuralyov TryBot-Result: Gopher Robot --- src/reflect/all_test.go | 40 +++++++++++++++++++++++++++++++++++----- src/reflect/value.go | 22 ++++++++++++++++++++-- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 3a360b6c71..3ba6cc2d51 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -47,6 +47,8 @@ type T struct { d *int } +var _ = T{} == T{} // tests depend on T being comparable + type pair struct { i any s string @@ -1364,9 +1366,14 @@ func TestIsZero(t *testing.T) { {uintptr(128), false}, // Array {Zero(TypeOf([5]string{})).Interface(), true}, - {[5]string{"", "", "", "", ""}, true}, - {[5]string{}, true}, - {[5]string{"", "", "", "a", ""}, false}, + {[5]string{}, true}, // comparable array + {[5]string{"", "", "", "a", ""}, false}, // comparable array + {[1]*int{}, true}, // direct pointer array + {[1]*int{new(int)}, false}, // direct pointer array + {[3][]int{}, true}, // incomparable array + {[3][]int{{1}}, false}, // incomparable array + {[1 << 12]byte{}, true}, + {[1 << 12]byte{1}, false}, // Chan {(chan string)(nil), true}, {make(chan string), false}, @@ -1393,8 +1400,12 @@ func TestIsZero(t *testing.T) { {"", true}, {"not-zero", false}, // Structs - {T{}, true}, - {T{123, 456.75, "hello", &_i}, false}, + {T{}, true}, // comparable struct + {T{123, 456.75, "hello", &_i}, false}, // comparable struct + {struct{ p *int }{}, true}, // direct pointer struct + {struct{ p *int }{new(int)}, false}, // direct pointer struct + {struct{ s []int }{}, true}, // incomparable struct + {struct{ s []int }{[]int{1}}, false}, // incomparable struct // UnsafePointer {(unsafe.Pointer)(nil), true}, {(unsafe.Pointer)(new(int)), false}, @@ -1426,6 +1437,25 @@ func TestIsZero(t *testing.T) { }() } +func BenchmarkIsZero(b *testing.B) { + source := ValueOf(struct { + ArrayComparable [4]T + ArrayIncomparable [4]_Complex + StructComparable T + StructIncomparable _Complex + }{}) + + for i := 0; i < source.NumField(); i++ { + name := source.Type().Field(i).Name + value := source.Field(i) + b.Run(name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + sink = value.IsZero() + } + }) + } +} + func TestInterfaceExtraction(t *testing.T) { var s struct { W io.Writer diff --git a/src/reflect/value.go b/src/reflect/value.go index 02add5a768..95bf4682aa 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1579,7 +1579,16 @@ func (v Value) IsZero() bool { c := v.Complex() return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 case Array: - for i := 0; i < v.Len(); i++ { + // If the type is comparable, then compare directly with zero. + if v.typ.equal != nil && v.typ.size <= maxZero { + if v.flag&flagIndir == 0 { + return v.ptr == nil + } + return v.typ.equal(v.ptr, unsafe.Pointer(&zeroVal[0])) + } + + n := v.Len() + for i := 0; i < n; i++ { if !v.Index(i).IsZero() { return false } @@ -1590,7 +1599,16 @@ func (v Value) IsZero() bool { case String: return v.Len() == 0 case Struct: - for i := 0; i < v.NumField(); i++ { + // If the type is comparable, then compare directly with zero. + if v.typ.equal != nil && v.typ.size <= maxZero { + if v.flag&flagIndir == 0 { + return v.ptr == nil + } + return v.typ.equal(v.ptr, unsafe.Pointer(&zeroVal[0])) + } + + n := v.NumField() + for i := 0; i < n; i++ { if !v.Field(i).IsZero() { return false } -- 2.48.1