]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: optimize Value.IsZero
authorJoe Tsai <joetsai@digital-static.net>
Fri, 10 Jun 2022 01:32:53 +0000 (18:32 -0700)
committerJoseph Tsai <joetsai@digital-static.net>
Wed, 24 Aug 2022 08:17:52 +0000 (08:17 +0000)
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 <dan@kortschak.io>
Run-TryBot: Joseph Tsai <joetsai@digital-static.net>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/reflect/all_test.go
src/reflect/value.go

index 3a360b6c71b9f44dfc35e298baefe643c71e63a5..3ba6cc2d51cde901a27f5c1b5e387a03a5d388cc 100644 (file)
@@ -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
index 02add5a768437fee8748e4d87742b63c66334814..95bf4682aa6fa9c72f492b3daa28b0e452f13c52 100644 (file)
@@ -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
                        }