]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: add fast paths for common, simple Kinds to DeepEqual
authorJosh Bleecher Snyder <josharian@gmail.com>
Sat, 8 May 2021 01:39:36 +0000 (18:39 -0700)
committerJosh Bleecher Snyder <josharian@gmail.com>
Mon, 13 Sep 2021 20:17:24 +0000 (20:17 +0000)
Though the normal equality check suffices, it allocates.
Handle common, simple kinds without allocating.

For some real world types compared with DeepEqual
in the Tailscale code base, the impact of these changes:

name             old time/op    new time/op    delta
HostInfoEqual-8    25.9µs ± 0%    14.4µs ± 0%  -44.32%  (p=0.008 n=5+5)

name             old alloc/op   new alloc/op   delta
HostInfoEqual-8    18.8kB ± 0%    12.6kB ± 0%  -32.58%  (p=0.008 n=5+5)

name             old allocs/op  new allocs/op  delta
HostInfoEqual-8       548 ± 0%        90 ± 0%  -83.58%  (p=0.008 n=5+5)

For the benchmarks added in this commit:

name                      old time/op    new time/op    delta
DeepEqual/int8-8            40.1ns ± 4%    31.3ns ± 4%   -21.79%  (p=0.000 n=19+20)
DeepEqual/[]int8-8           169ns ± 1%     123ns ± 1%   -27.05%  (p=0.000 n=17+18)
DeepEqual/int16-8           39.9ns ± 2%    30.8ns ± 4%   -22.81%  (p=0.000 n=17+20)
DeepEqual/[]int16-8          172ns ± 0%     122ns ± 1%   -28.91%  (p=0.000 n=17+18)
DeepEqual/int32-8           40.4ns ± 3%    31.1ns ± 4%   -23.07%  (p=0.000 n=20+20)
DeepEqual/[]int32-8          180ns ± 1%     124ns ± 1%   -31.06%  (p=0.000 n=20+18)
DeepEqual/int64-8           40.1ns ± 2%    31.4ns ± 4%   -21.82%  (p=0.000 n=20+20)
DeepEqual/[]int64-8          180ns ± 1%     124ns ± 1%   -31.47%  (p=0.000 n=16+19)
DeepEqual/int-8             40.1ns ± 4%    30.9ns ± 3%   -22.88%  (p=0.000 n=20+18)
DeepEqual/[]int-8            180ns ± 0%     123ns ± 2%   -31.59%  (p=0.000 n=19+20)
DeepEqual/uint8-8           39.8ns ± 3%    31.9ns ± 5%   -19.72%  (p=0.000 n=20+20)
DeepEqual/[]uint8-8          168ns ± 1%     114ns ± 1%   -32.48%  (p=0.000 n=18+19)
DeepEqual/uint16-8          40.3ns ± 4%    31.4ns ± 6%   -22.14%  (p=0.000 n=20+20)
DeepEqual/[]uint16-8         173ns ± 1%     124ns ± 1%   -28.20%  (p=0.000 n=20+16)
DeepEqual/uint32-8          40.1ns ± 3%    30.7ns ± 3%   -23.48%  (p=0.000 n=20+20)
DeepEqual/[]uint32-8         180ns ± 1%     123ns ± 1%   -31.56%  (p=0.000 n=20+18)
DeepEqual/uint64-8          40.0ns ± 4%    31.3ns ± 4%   -21.80%  (p=0.000 n=19+19)
DeepEqual/[]uint64-8         180ns ± 1%     124ns ± 0%   -31.45%  (p=0.000 n=18+18)
DeepEqual/uint-8            39.8ns ± 3%    31.1ns ± 4%   -21.95%  (p=0.000 n=19+20)
DeepEqual/[]uint-8           181ns ± 1%     122ns ± 1%   -32.33%  (p=0.000 n=17+20)
DeepEqual/uintptr-8         40.3ns ± 3%    31.2ns ± 3%   -22.66%  (p=0.000 n=20+18)
DeepEqual/[]uintptr-8        181ns ± 1%     124ns ± 1%   -31.46%  (p=0.000 n=19+16)
DeepEqual/float32-8         40.3ns ± 2%    31.2ns ± 3%   -22.52%  (p=0.000 n=16+20)
DeepEqual/[]float32-8        180ns ± 1%     122ns ± 1%   -32.18%  (p=0.000 n=17+17)
DeepEqual/float64-8         40.6ns ± 3%    30.9ns ± 5%   -23.91%  (p=0.000 n=19+20)
DeepEqual/[]float64-8        182ns ± 2%     121ns ± 1%   -33.33%  (p=0.000 n=18+20)
DeepEqual/complex64-8       43.0ns ±11%    32.1ns ± 5%   -25.33%  (p=0.000 n=20+18)
DeepEqual/[]complex64-8      182ns ± 1%     122ns ± 2%   -32.60%  (p=0.000 n=18+19)
DeepEqual/complex128-8      42.4ns ± 4%    34.2ns ± 3%   -19.35%  (p=0.000 n=20+19)
DeepEqual/[]complex128-8     197ns ± 1%     122ns ± 1%   -38.01%  (p=0.000 n=19+19)
DeepEqual/bool-8            40.3ns ± 3%    32.9ns ± 5%   -18.33%  (p=0.000 n=20+20)
DeepEqual/[]bool-8           169ns ± 1%     124ns ± 1%   -26.90%  (p=0.000 n=18+19)
DeepEqual/string-8          41.4ns ± 3%    33.7ns ± 5%   -18.50%  (p=0.000 n=19+20)
DeepEqual/[]string-8         216ns ± 0%     128ns ± 1%   -41.05%  (p=0.000 n=19+17)
DeepEqual/[]uint8#01-8       507ns ± 1%     112ns ± 2%   -77.92%  (p=0.000 n=20+20)
DeepEqual/[][]uint8-8        613ns ± 1%     210ns ± 1%   -65.76%  (p=0.000 n=18+19)
DeepEqual/[6]uint8-8         228ns ± 1%     162ns ± 1%   -29.00%  (p=0.000 n=20+19)
DeepEqual/[][6]uint8-8       546ns ± 2%     269ns ± 1%   -50.72%  (p=0.000 n=20+19)

name                      old alloc/op   new alloc/op   delta
DeepEqual/int8-8             0.00B          0.00B           ~     (all equal)
DeepEqual/[]int8-8           2.00B ± 0%     0.00B       -100.00%  (p=0.000 n=20+20)
DeepEqual/int16-8            0.00B          0.00B           ~     (all equal)
DeepEqual/[]int16-8          4.00B ± 0%     0.00B       -100.00%  (p=0.000 n=20+20)
DeepEqual/int32-8            0.00B          0.00B           ~     (all equal)
DeepEqual/[]int32-8          8.00B ± 0%     0.00B       -100.00%  (p=0.000 n=20+20)
DeepEqual/int64-8            0.00B          0.00B           ~     (all equal)
DeepEqual/[]int64-8          16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/int-8              0.00B          0.00B           ~     (all equal)
DeepEqual/[]int-8            16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/uint8-8            0.00B          0.00B           ~     (all equal)
DeepEqual/[]uint8-8          2.00B ± 0%     0.00B       -100.00%  (p=0.000 n=20+20)
DeepEqual/uint16-8           0.00B          0.00B           ~     (all equal)
DeepEqual/[]uint16-8         4.00B ± 0%     0.00B       -100.00%  (p=0.000 n=20+20)
DeepEqual/uint32-8           0.00B          0.00B           ~     (all equal)
DeepEqual/[]uint32-8         8.00B ± 0%     0.00B       -100.00%  (p=0.000 n=20+20)
DeepEqual/uint64-8           0.00B          0.00B           ~     (all equal)
DeepEqual/[]uint64-8         16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/uint-8             0.00B          0.00B           ~     (all equal)
DeepEqual/[]uint-8           16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/uintptr-8          0.00B          0.00B           ~     (all equal)
DeepEqual/[]uintptr-8        16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/float32-8          0.00B          0.00B           ~     (all equal)
DeepEqual/[]float32-8        8.00B ± 0%     0.00B       -100.00%  (p=0.000 n=20+20)
DeepEqual/float64-8          0.00B          0.00B           ~     (all equal)
DeepEqual/[]float64-8        16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/complex64-8        0.00B          0.00B           ~     (all equal)
DeepEqual/[]complex64-8      16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/complex128-8       0.00B          0.00B           ~     (all equal)
DeepEqual/[]complex128-8     32.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/bool-8             0.00B          0.00B           ~     (all equal)
DeepEqual/[]bool-8           2.00B ± 0%     0.00B       -100.00%  (p=0.000 n=20+20)
DeepEqual/string-8           0.00B          0.00B           ~     (all equal)
DeepEqual/[]string-8         32.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/[]uint8#01-8       12.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/[][]uint8-8        12.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)
DeepEqual/[6]uint8-8         0.00B          0.00B           ~     (all equal)
DeepEqual/[][6]uint8-8       12.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)

name                      old allocs/op  new allocs/op  delta
DeepEqual/int8-8              0.00           0.00           ~     (all equal)
DeepEqual/[]int8-8            2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/int16-8             0.00           0.00           ~     (all equal)
DeepEqual/[]int16-8           2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/int32-8             0.00           0.00           ~     (all equal)
DeepEqual/[]int32-8           2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/int64-8             0.00           0.00           ~     (all equal)
DeepEqual/[]int64-8           2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/int-8               0.00           0.00           ~     (all equal)
DeepEqual/[]int-8             2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/uint8-8             0.00           0.00           ~     (all equal)
DeepEqual/[]uint8-8           2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/uint16-8            0.00           0.00           ~     (all equal)
DeepEqual/[]uint16-8          2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/uint32-8            0.00           0.00           ~     (all equal)
DeepEqual/[]uint32-8          2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/uint64-8            0.00           0.00           ~     (all equal)
DeepEqual/[]uint64-8          2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/uint-8              0.00           0.00           ~     (all equal)
DeepEqual/[]uint-8            2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/uintptr-8           0.00           0.00           ~     (all equal)
DeepEqual/[]uintptr-8         2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/float32-8           0.00           0.00           ~     (all equal)
DeepEqual/[]float32-8         2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/float64-8           0.00           0.00           ~     (all equal)
DeepEqual/[]float64-8         2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/complex64-8         0.00           0.00           ~     (all equal)
DeepEqual/[]complex64-8       2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/complex128-8        0.00           0.00           ~     (all equal)
DeepEqual/[]complex128-8      2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/bool-8              0.00           0.00           ~     (all equal)
DeepEqual/[]bool-8            2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/string-8            0.00           0.00           ~     (all equal)
DeepEqual/[]string-8          2.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)
DeepEqual/[]uint8#01-8        12.0 ± 0%       0.0       -100.00%  (p=0.000 n=20+20)
DeepEqual/[][]uint8-8         12.0 ± 0%       0.0       -100.00%  (p=0.000 n=20+20)
DeepEqual/[6]uint8-8          0.00           0.00           ~     (all equal)
DeepEqual/[][6]uint8-8        12.0 ± 0%       0.0       -100.00%  (p=0.000 n=20+20)

Change-Id: Ic21f0e2305f2cf5e6674c81b9ca609120b3006d9
Reviewed-on: https://go-review.googlesource.com/c/go/+/318169
Trust: Josh Bleecher Snyder <josharian@gmail.com>
Trust: Joe Tsai <joetsai@digital-static.net>
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
Run-TryBot: Joe Tsai <joetsai@digital-static.net>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Joe Tsai <joetsai@digital-static.net>
src/reflect/all_test.go
src/reflect/deepequal.go

index e92f71135c4630f7ced11d3bde55ca7a44936abc..5b147082bb1f2befa1db79ffbf1a8d9b44f60976 100644 (file)
@@ -905,6 +905,9 @@ var deepEqualTests = []DeepEqualTest{
        {error(nil), error(nil), true},
        {map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, true},
        {fn1, fn2, true},
+       {[]byte{1, 2, 3}, []byte{1, 2, 3}, true},
+       {[]MyByte{1, 2, 3}, []MyByte{1, 2, 3}, true},
+       {MyBytes{1, 2, 3}, MyBytes{1, 2, 3}, true},
 
        // Inequalities
        {1, 2, false},
@@ -950,6 +953,9 @@ var deepEqualTests = []DeepEqualTest{
        {&[3]interface{}{1, 2, 4}, &[3]interface{}{1, 2, "s"}, false},
        {Basic{1, 0.5}, NotBasic{1, 0.5}, false},
        {map[uint]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, false},
+       {[]byte{1, 2, 3}, []MyByte{1, 2, 3}, false},
+       {[]MyByte{1, 2, 3}, MyBytes{1, 2, 3}, false},
+       {[]byte{1, 2, 3}, MyBytes{1, 2, 3}, false},
 
        // Possible loops.
        {&loop1, &loop1, true},
@@ -1049,6 +1055,82 @@ func TestDeepEqualUnexportedMap(t *testing.T) {
        }
 }
 
+var deepEqualPerfTests = []struct {
+       x, y interface{}
+}{
+       {x: int8(99), y: int8(99)},
+       {x: []int8{99}, y: []int8{99}},
+       {x: int16(99), y: int16(99)},
+       {x: []int16{99}, y: []int16{99}},
+       {x: int32(99), y: int32(99)},
+       {x: []int32{99}, y: []int32{99}},
+       {x: int64(99), y: int64(99)},
+       {x: []int64{99}, y: []int64{99}},
+       {x: int(999999), y: int(999999)},
+       {x: []int{999999}, y: []int{999999}},
+
+       {x: uint8(99), y: uint8(99)},
+       {x: []uint8{99}, y: []uint8{99}},
+       {x: uint16(99), y: uint16(99)},
+       {x: []uint16{99}, y: []uint16{99}},
+       {x: uint32(99), y: uint32(99)},
+       {x: []uint32{99}, y: []uint32{99}},
+       {x: uint64(99), y: uint64(99)},
+       {x: []uint64{99}, y: []uint64{99}},
+       {x: uint(999999), y: uint(999999)},
+       {x: []uint{999999}, y: []uint{999999}},
+       {x: uintptr(999999), y: uintptr(999999)},
+       {x: []uintptr{999999}, y: []uintptr{999999}},
+
+       {x: float32(1.414), y: float32(1.414)},
+       {x: []float32{1.414}, y: []float32{1.414}},
+       {x: float64(1.414), y: float64(1.414)},
+       {x: []float64{1.414}, y: []float64{1.414}},
+
+       {x: complex64(1.414), y: complex64(1.414)},
+       {x: []complex64{1.414}, y: []complex64{1.414}},
+       {x: complex128(1.414), y: complex128(1.414)},
+       {x: []complex128{1.414}, y: []complex128{1.414}},
+
+       {x: true, y: true},
+       {x: []bool{true}, y: []bool{true}},
+
+       {x: "abcdef", y: "abcdef"},
+       {x: []string{"abcdef"}, y: []string{"abcdef"}},
+
+       {x: []byte("abcdef"), y: []byte("abcdef")},
+       {x: [][]byte{[]byte("abcdef")}, y: [][]byte{[]byte("abcdef")}},
+
+       {x: [6]byte{'a', 'b', 'c', 'a', 'b', 'c'}, y: [6]byte{'a', 'b', 'c', 'a', 'b', 'c'}},
+       {x: [][6]byte{[6]byte{'a', 'b', 'c', 'a', 'b', 'c'}}, y: [][6]byte{[6]byte{'a', 'b', 'c', 'a', 'b', 'c'}}},
+}
+
+func TestDeepEqualAllocs(t *testing.T) {
+       for _, tt := range deepEqualPerfTests {
+               t.Run(ValueOf(tt.x).Type().String(), func(t *testing.T) {
+                       got := testing.AllocsPerRun(100, func() {
+                               if !DeepEqual(tt.x, tt.y) {
+                                       t.Errorf("DeepEqual(%v, %v)=false", tt.x, tt.y)
+                               }
+                       })
+                       if int(got) != 0 {
+                               t.Errorf("DeepEqual(%v, %v) allocated %d times", tt.x, tt.y, int(got))
+                       }
+               })
+       }
+}
+
+func BenchmarkDeepEqual(b *testing.B) {
+       for _, bb := range deepEqualPerfTests {
+               b.Run(ValueOf(bb.x).Type().String(), func(b *testing.B) {
+                       b.ReportAllocs()
+                       for i := 0; i < b.N; i++ {
+                               sink = DeepEqual(bb.x, bb.y)
+                       }
+               })
+       }
+}
+
 func check2ndField(x interface{}, offs uintptr, t *testing.T) {
        s := ValueOf(x)
        f := s.Type().Field(1)
index d951d8d9997b36075b230c8d3ea1b647465c59db..94174dec045c98949b474ef074bce77955f472c2 100644 (file)
@@ -6,7 +6,10 @@
 
 package reflect
 
-import "unsafe"
+import (
+       "internal/bytealg"
+       "unsafe"
+)
 
 // During deepValueEqual, must keep track of checks that are
 // in progress. The comparison algorithm assumes that all
@@ -102,6 +105,10 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
                if v1.Pointer() == v2.Pointer() {
                        return true
                }
+               // Special case for []byte, which is common.
+               if v1.Type().Elem().Kind() == Uint8 {
+                       return bytealg.Equal(v1.Bytes(), v2.Bytes())
+               }
                for i := 0; i < v1.Len(); i++ {
                        if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
                                return false
@@ -149,6 +156,18 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
                }
                // Can't do better than this:
                return false
+       case Int, Int8, Int16, Int32, Int64:
+               return v1.Int() == v2.Int()
+       case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
+               return v1.Uint() == v2.Uint()
+       case String:
+               return v1.String() == v2.String()
+       case Bool:
+               return v1.Bool() == v2.Bool()
+       case Float32, Float64:
+               return v1.Float() == v2.Float()
+       case Complex64, Complex128:
+               return v1.Complex() == v2.Complex()
        default:
                // Normal equality suffices
                return valueInterface(v1, false) == valueInterface(v2, false)