Skip to content

Commit a0c409c

Browse files
committed
reflect: add fast paths for common, simple Kinds to DeepEqual
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 <[email protected]> Trust: Joe Tsai <[email protected]> Run-TryBot: Josh Bleecher Snyder <[email protected]> Run-TryBot: Joe Tsai <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Joe Tsai <[email protected]>
1 parent ac40c98 commit a0c409c

File tree

2 files changed

+102
-1
lines changed

2 files changed

+102
-1
lines changed

src/reflect/all_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,9 @@ var deepEqualTests = []DeepEqualTest{
905905
{error(nil), error(nil), true},
906906
{map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, true},
907907
{fn1, fn2, true},
908+
{[]byte{1, 2, 3}, []byte{1, 2, 3}, true},
909+
{[]MyByte{1, 2, 3}, []MyByte{1, 2, 3}, true},
910+
{MyBytes{1, 2, 3}, MyBytes{1, 2, 3}, true},
908911

909912
// Inequalities
910913
{1, 2, false},
@@ -950,6 +953,9 @@ var deepEqualTests = []DeepEqualTest{
950953
{&[3]interface{}{1, 2, 4}, &[3]interface{}{1, 2, "s"}, false},
951954
{Basic{1, 0.5}, NotBasic{1, 0.5}, false},
952955
{map[uint]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, false},
956+
{[]byte{1, 2, 3}, []MyByte{1, 2, 3}, false},
957+
{[]MyByte{1, 2, 3}, MyBytes{1, 2, 3}, false},
958+
{[]byte{1, 2, 3}, MyBytes{1, 2, 3}, false},
953959

954960
// Possible loops.
955961
{&loop1, &loop1, true},
@@ -1049,6 +1055,82 @@ func TestDeepEqualUnexportedMap(t *testing.T) {
10491055
}
10501056
}
10511057

1058+
var deepEqualPerfTests = []struct {
1059+
x, y interface{}
1060+
}{
1061+
{x: int8(99), y: int8(99)},
1062+
{x: []int8{99}, y: []int8{99}},
1063+
{x: int16(99), y: int16(99)},
1064+
{x: []int16{99}, y: []int16{99}},
1065+
{x: int32(99), y: int32(99)},
1066+
{x: []int32{99}, y: []int32{99}},
1067+
{x: int64(99), y: int64(99)},
1068+
{x: []int64{99}, y: []int64{99}},
1069+
{x: int(999999), y: int(999999)},
1070+
{x: []int{999999}, y: []int{999999}},
1071+
1072+
{x: uint8(99), y: uint8(99)},
1073+
{x: []uint8{99}, y: []uint8{99}},
1074+
{x: uint16(99), y: uint16(99)},
1075+
{x: []uint16{99}, y: []uint16{99}},
1076+
{x: uint32(99), y: uint32(99)},
1077+
{x: []uint32{99}, y: []uint32{99}},
1078+
{x: uint64(99), y: uint64(99)},
1079+
{x: []uint64{99}, y: []uint64{99}},
1080+
{x: uint(999999), y: uint(999999)},
1081+
{x: []uint{999999}, y: []uint{999999}},
1082+
{x: uintptr(999999), y: uintptr(999999)},
1083+
{x: []uintptr{999999}, y: []uintptr{999999}},
1084+
1085+
{x: float32(1.414), y: float32(1.414)},
1086+
{x: []float32{1.414}, y: []float32{1.414}},
1087+
{x: float64(1.414), y: float64(1.414)},
1088+
{x: []float64{1.414}, y: []float64{1.414}},
1089+
1090+
{x: complex64(1.414), y: complex64(1.414)},
1091+
{x: []complex64{1.414}, y: []complex64{1.414}},
1092+
{x: complex128(1.414), y: complex128(1.414)},
1093+
{x: []complex128{1.414}, y: []complex128{1.414}},
1094+
1095+
{x: true, y: true},
1096+
{x: []bool{true}, y: []bool{true}},
1097+
1098+
{x: "abcdef", y: "abcdef"},
1099+
{x: []string{"abcdef"}, y: []string{"abcdef"}},
1100+
1101+
{x: []byte("abcdef"), y: []byte("abcdef")},
1102+
{x: [][]byte{[]byte("abcdef")}, y: [][]byte{[]byte("abcdef")}},
1103+
1104+
{x: [6]byte{'a', 'b', 'c', 'a', 'b', 'c'}, y: [6]byte{'a', 'b', 'c', 'a', 'b', 'c'}},
1105+
{x: [][6]byte{[6]byte{'a', 'b', 'c', 'a', 'b', 'c'}}, y: [][6]byte{[6]byte{'a', 'b', 'c', 'a', 'b', 'c'}}},
1106+
}
1107+
1108+
func TestDeepEqualAllocs(t *testing.T) {
1109+
for _, tt := range deepEqualPerfTests {
1110+
t.Run(ValueOf(tt.x).Type().String(), func(t *testing.T) {
1111+
got := testing.AllocsPerRun(100, func() {
1112+
if !DeepEqual(tt.x, tt.y) {
1113+
t.Errorf("DeepEqual(%v, %v)=false", tt.x, tt.y)
1114+
}
1115+
})
1116+
if int(got) != 0 {
1117+
t.Errorf("DeepEqual(%v, %v) allocated %d times", tt.x, tt.y, int(got))
1118+
}
1119+
})
1120+
}
1121+
}
1122+
1123+
func BenchmarkDeepEqual(b *testing.B) {
1124+
for _, bb := range deepEqualPerfTests {
1125+
b.Run(ValueOf(bb.x).Type().String(), func(b *testing.B) {
1126+
b.ReportAllocs()
1127+
for i := 0; i < b.N; i++ {
1128+
sink = DeepEqual(bb.x, bb.y)
1129+
}
1130+
})
1131+
}
1132+
}
1133+
10521134
func check2ndField(x interface{}, offs uintptr, t *testing.T) {
10531135
s := ValueOf(x)
10541136
f := s.Type().Field(1)

src/reflect/deepequal.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
package reflect
88

9-
import "unsafe"
9+
import (
10+
"internal/bytealg"
11+
"unsafe"
12+
)
1013

1114
// During deepValueEqual, must keep track of checks that are
1215
// in progress. The comparison algorithm assumes that all
@@ -102,6 +105,10 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
102105
if v1.Pointer() == v2.Pointer() {
103106
return true
104107
}
108+
// Special case for []byte, which is common.
109+
if v1.Type().Elem().Kind() == Uint8 {
110+
return bytealg.Equal(v1.Bytes(), v2.Bytes())
111+
}
105112
for i := 0; i < v1.Len(); i++ {
106113
if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
107114
return false
@@ -149,6 +156,18 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
149156
}
150157
// Can't do better than this:
151158
return false
159+
case Int, Int8, Int16, Int32, Int64:
160+
return v1.Int() == v2.Int()
161+
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
162+
return v1.Uint() == v2.Uint()
163+
case String:
164+
return v1.String() == v2.String()
165+
case Bool:
166+
return v1.Bool() == v2.Bool()
167+
case Float32, Float64:
168+
return v1.Float() == v2.Float()
169+
case Complex64, Complex128:
170+
return v1.Complex() == v2.Complex()
152171
default:
153172
// Normal equality suffices
154173
return valueInterface(v1, false) == valueInterface(v2, false)

0 commit comments

Comments
 (0)