From 52006fa3e34c50a2e7a642bdb04192db94b21468 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Tue, 19 Dec 2023 19:45:58 +0800 Subject: [PATCH 1/8] add benchmark --- src/reflect/benchmark_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/reflect/benchmark_test.go b/src/reflect/benchmark_test.go index 2e701b062e..131377e618 100644 --- a/src/reflect/benchmark_test.go +++ b/src/reflect/benchmark_test.go @@ -119,6 +119,14 @@ func BenchmarkMapsDeepEqual(b *testing.B) { } } +func BenchmarkBigSliceDeepEqual(b *testing.B) { + var s1, s2 = make([]int16, 1024), make([]int16, 1024) + s2[2] = 9 + for i := 0; i < b.N; i++ { + DeepEqual(s1, s2) + } +} + func BenchmarkIsZero(b *testing.B) { type Int4 struct { a, b, c, d int From 662f585435948bad837842d04bbaa2b3ae4ae505 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Tue, 19 Dec 2023 19:54:10 +0800 Subject: [PATCH 2/8] optimize deepequal for slice typs --- src/reflect/deepequal.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index 961e170118..b23a96ac60 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -7,6 +7,7 @@ package reflect import ( + "internal/abi" "internal/bytealg" "unsafe" ) @@ -105,9 +106,11 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { if v1.UnsafePointer() == v2.UnsafePointer() { return true } - // Special case for []byte, which is common. - if v1.Type().Elem().Kind() == Uint8 { - return bytealg.Equal(v1.Bytes(), v2.Bytes()) + if v1.typ_.TFlag == abi.TFlagRegularMemory { + return bytealg.Equal( + unsafe.Slice((*byte)(v1.ptr), v1.typ_.Elem().Size_*uintptr(v1.Len())), + unsafe.Slice((*byte)(v2.ptr), v2.typ_.Elem().Size_*uintptr(v2.Len())), + ) } for i := 0; i < v1.Len(); i++ { if !deepValueEqual(v1.Index(i), v2.Index(i), visited) { From 8ae102ad9b54b1e321643a2644ad7bfd97f5a6f1 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Tue, 19 Dec 2023 20:57:06 +0800 Subject: [PATCH 3/8] fix --- src/reflect/deepequal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index b23a96ac60..f267bca1e3 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -106,7 +106,7 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { if v1.UnsafePointer() == v2.UnsafePointer() { return true } - if v1.typ_.TFlag == abi.TFlagRegularMemory { + if v1.typ_.TFlag&abi.TFlagRegularMemory != 0 { return bytealg.Equal( unsafe.Slice((*byte)(v1.ptr), v1.typ_.Elem().Size_*uintptr(v1.Len())), unsafe.Slice((*byte)(v2.ptr), v2.typ_.Elem().Size_*uintptr(v2.Len())), From 3fb5ab2e180958829a4b57ce6bd2f53442630750 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Fri, 22 Dec 2023 19:49:25 +0800 Subject: [PATCH 4/8] fix2 --- src/reflect/all_test.go | 15 +++++++++++++++ src/reflect/benchmark_test.go | 2 +- src/reflect/deepequal.go | 35 ++++++++++++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index e77537c9a5..b347ae4124 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -1054,8 +1054,20 @@ func init() { cycleMap2["cycle"] = cycleMap2 cycleMap3 = map[string]any{} cycleMap3["different"] = cycleMap3 + + p1.b = append(p1.b, p2, p1, p1) + p2.b = append(p2.b, p1, p2, p1) } +type slicePtr struct { + b []*slicePtr +} + +var ( + p1 = new(slicePtr) + p2 = new(slicePtr) +) + var deepEqualTests = []DeepEqualTest{ // Equalities {nil, nil, true}, @@ -1073,6 +1085,8 @@ var deepEqualTests = []DeepEqualTest{ {[]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}, + {[]int32{1, 2, 3}, []int32{1, 2, 3}, true}, + {p1, p2, true}, // Inequalities {1, 2, false}, @@ -1094,6 +1108,7 @@ var deepEqualTests = []DeepEqualTest{ {fn3, fn3, false}, {[][]int{{1}}, [][]int{{2}}, false}, {&structWithSelfPtr{p: &structWithSelfPtr{s: "a"}}, &structWithSelfPtr{p: &structWithSelfPtr{s: "b"}}, false}, + {[]int32{1, 2, 3}, []int32{2, 1, 3}, false}, // Fun with floating point. {math.NaN(), math.NaN(), false}, diff --git a/src/reflect/benchmark_test.go b/src/reflect/benchmark_test.go index 131377e618..c77954d09e 100644 --- a/src/reflect/benchmark_test.go +++ b/src/reflect/benchmark_test.go @@ -120,7 +120,7 @@ func BenchmarkMapsDeepEqual(b *testing.B) { } func BenchmarkBigSliceDeepEqual(b *testing.B) { - var s1, s2 = make([]int16, 1024), make([]int16, 1024) + var s1, s2 = make([]int16, 10240), make([]int16, 10240) s2[2] = 9 for i := 0; i < b.N; i++ { DeepEqual(s1, s2) diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index f267bca1e3..deb5a1f83b 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -106,10 +106,12 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { if v1.UnsafePointer() == v2.UnsafePointer() { return true } - if v1.typ_.TFlag&abi.TFlagRegularMemory != 0 { + // Special case raw memory. Particularly, []byte is very common and is handled here. + typ := (*abi.SliceType)(unsafe.Pointer(v1.typ())) + if isDeepEqualRawMemory(typ.Elem) { return bytealg.Equal( - unsafe.Slice((*byte)(v1.ptr), v1.typ_.Elem().Size_*uintptr(v1.Len())), - unsafe.Slice((*byte)(v2.ptr), v2.typ_.Elem().Size_*uintptr(v2.Len())), + unsafe.Slice(*(**byte)(v1.ptr), v1.typ_.Elem().Size_*uintptr(v1.Len())), + unsafe.Slice(*(**byte)(v2.ptr), v2.typ_.Elem().Size_*uintptr(v2.Len())), ) } for i := 0; i < v1.Len(); i++ { @@ -178,6 +180,33 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { } } +// isDeepEqualRawMemory return +// whether DeepEqual can treat a type of data +// as a single typ.size byte area to +// compare whether the depth is equal +func isDeepEqualRawMemory(typ *abi.Type) (ok bool) { + // Note: Here is an incorrect implementation : + // + // return typ.TFlag==abi.TFlagRegularMemory + // + // The reason is that DeepEqual + // cannot treat a pointer as a single area of a pointer size, + // because when DeepEqual + // compares two previously compared pointer values a second time and later, + // it treats them as equal + + // TODO: Find a way to quickly determine whether a struct contains Pointer + // Make struct do not have Pointer return true + + switch typ.Kind() { + case abi.Int8, abi.Int16, abi.Int32, abi.Int64, abi.Uintptr, abi.Uint8, abi.Uint16, abi.Uint32, abi.Uint64, abi.Bool: + return true + case abi.Array, abi.Slice: + return isDeepEqualRawMemory(typ.Elem()) + } + return false +} + // DeepEqual reports whether x and y are “deeply equal,” defined as follows. // Two values of identical type are deeply equal if one of the following cases applies. // Values of distinct types are never deeply equal. From 1455d70233e6d965037b2388248a04d1dd6f44f1 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Fri, 22 Dec 2023 20:47:05 +0800 Subject: [PATCH 5/8] fix3 --- src/reflect/deepequal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index deb5a1f83b..f4cf6ff056 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -201,7 +201,7 @@ func isDeepEqualRawMemory(typ *abi.Type) (ok bool) { switch typ.Kind() { case abi.Int8, abi.Int16, abi.Int32, abi.Int64, abi.Uintptr, abi.Uint8, abi.Uint16, abi.Uint32, abi.Uint64, abi.Bool: return true - case abi.Array, abi.Slice: + case abi.Array: return isDeepEqualRawMemory(typ.Elem()) } return false From 80ad3bac1be8463ad06e2c912d4ee065872d0184 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Tue, 9 Jan 2024 18:13:10 +0800 Subject: [PATCH 6/8] fix4 --- src/reflect/all_test.go | 5 +++++ src/reflect/deepequal.go | 42 +++++++++++++++++----------------------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index b347ae4124..d074ca3b7e 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -1087,6 +1087,7 @@ var deepEqualTests = []DeepEqualTest{ {MyBytes{1, 2, 3}, MyBytes{1, 2, 3}, true}, {[]int32{1, 2, 3}, []int32{1, 2, 3}, true}, {p1, p2, true}, + {ptr(1), ptr(1), true}, // Inequalities {1, 2, false}, @@ -1148,6 +1149,10 @@ var deepEqualTests = []DeepEqualTest{ {&cycleMap1, &cycleMap3, false}, } +func ptr[T any](a T) *T { + return &a +} + func TestDeepEqual(t *testing.T) { for _, test := range deepEqualTests { if test.b == (self{}) { diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index f4cf6ff056..6668dd4411 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -100,18 +100,22 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { if v1.IsNil() != v2.IsNil() { return false } - if v1.Len() != v2.Len() { + len := v1.Len() + if len != v2.Len() { return false } - if v1.UnsafePointer() == v2.UnsafePointer() { + v1ptr := v1.UnsafePointer() + v2ptr := v2.UnsafePointer() + if v1ptr == v2ptr { return true } // Special case raw memory. Particularly, []byte is very common and is handled here. - typ := (*abi.SliceType)(unsafe.Pointer(v1.typ())) - if isDeepEqualRawMemory(typ.Elem) { + elem := v1.typ().Elem() + if isDeepEqualRawMemory(elem) { + size := elem.Size_ * uintptr(len) return bytealg.Equal( - unsafe.Slice(*(**byte)(v1.ptr), v1.typ_.Elem().Size_*uintptr(v1.Len())), - unsafe.Slice(*(**byte)(v2.ptr), v2.typ_.Elem().Size_*uintptr(v2.Len())), + unsafe.Slice((*byte)(v1ptr), size), + unsafe.Slice((*byte)(v2ptr), size), ) } for i := 0; i < v1.Len(); i++ { @@ -180,31 +184,21 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { } } -// isDeepEqualRawMemory return -// whether DeepEqual can treat a type of data -// as a single typ.size byte area to -// compare whether the depth is equal +// isDeepEqualRawMemory reports +// DeepEqual can determine whether depths is equal +// by comparing byte by byte equality func isDeepEqualRawMemory(typ *abi.Type) (ok bool) { // Note: Here is an incorrect implementation : // // return typ.TFlag==abi.TFlagRegularMemory // - // The reason is that DeepEqual - // cannot treat a pointer as a single area of a pointer size, - // because when DeepEqual - // compares two previously compared pointer values a second time and later, + // The reason is DeepEqual can't + // determine whether two pointer values are equal in depth by comparing them byte by byte + // because when deeequal + // perform a second and subsequent comparison of two previously compared pointer values, // it treats them as equal - // TODO: Find a way to quickly determine whether a struct contains Pointer - // Make struct do not have Pointer return true - - switch typ.Kind() { - case abi.Int8, abi.Int16, abi.Int32, abi.Int64, abi.Uintptr, abi.Uint8, abi.Uint16, abi.Uint32, abi.Uint64, abi.Bool: - return true - case abi.Array: - return isDeepEqualRawMemory(typ.Elem()) - } - return false + return typ.PtrBytes == 0 && typ.TFlag&abi.TFlagRegularMemory != 0 } // DeepEqual reports whether x and y are “deeply equal,” defined as follows. From 5bc9e46dca1e29de91a670f9a6a380247898ebae Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Thu, 11 Jan 2024 12:11:11 +0800 Subject: [PATCH 7/8] fix5 --- src/reflect/all_test.go | 7 ++++++- src/reflect/benchmark_test.go | 8 -------- src/reflect/deepequal.go | 17 ++++++++--------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index d074ca3b7e..e4b8ddd224 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -1087,7 +1087,8 @@ var deepEqualTests = []DeepEqualTest{ {MyBytes{1, 2, 3}, MyBytes{1, 2, 3}, true}, {[]int32{1, 2, 3}, []int32{1, 2, 3}, true}, {p1, p2, true}, - {ptr(1), ptr(1), true}, + {[]*int{ptr(1), ptr(2)}, []*int{ptr(1), ptr(2)}, true}, + {[]int32{1, 2, 3, 4}[:3], []int32{1, 2, 3, 5}[:3], true}, // Inequalities {1, 2, false}, @@ -1110,6 +1111,8 @@ var deepEqualTests = []DeepEqualTest{ {[][]int{{1}}, [][]int{{2}}, false}, {&structWithSelfPtr{p: &structWithSelfPtr{s: "a"}}, &structWithSelfPtr{p: &structWithSelfPtr{s: "b"}}, false}, {[]int32{1, 2, 3}, []int32{2, 1, 3}, false}, + {[]*int{ptr(1), ptr(2)}, []*int{ptr(2), ptr(1)}, false}, + {[]int32{1, 2, 3, 4}, []int32{1, 2, 3, 5}, false}, // Fun with floating point. {math.NaN(), math.NaN(), false}, @@ -1290,6 +1293,8 @@ var deepEqualPerfTests = []struct { {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'}}}, + + {x: make([]int16, 10240), y: make([]int16, 10240)}, } func TestDeepEqualAllocs(t *testing.T) { diff --git a/src/reflect/benchmark_test.go b/src/reflect/benchmark_test.go index c77954d09e..2e701b062e 100644 --- a/src/reflect/benchmark_test.go +++ b/src/reflect/benchmark_test.go @@ -119,14 +119,6 @@ func BenchmarkMapsDeepEqual(b *testing.B) { } } -func BenchmarkBigSliceDeepEqual(b *testing.B) { - var s1, s2 = make([]int16, 10240), make([]int16, 10240) - s2[2] = 9 - for i := 0; i < b.N; i++ { - DeepEqual(s1, s2) - } -} - func BenchmarkIsZero(b *testing.B) { type Int4 struct { a, b, c, d int diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index 6668dd4411..88bc06eda7 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -184,19 +184,18 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { } } -// isDeepEqualRawMemory reports -// DeepEqual can determine whether depths is equal -// by comparing byte by byte equality +// isDeepEqualRawMemory reports whether DeepEqual can compare +// two instances of a type by just comparing their raw memory contents. func isDeepEqualRawMemory(typ *abi.Type) (ok bool) { // Note: Here is an incorrect implementation : // - // return typ.TFlag==abi.TFlagRegularMemory + // return typ.TFlag&abi.TFlagRegularMemory != 0 // - // The reason is DeepEqual can't - // determine whether two pointer values are equal in depth by comparing them byte by byte - // because when deeequal - // perform a second and subsequent comparison of two previously compared pointer values, - // it treats them as equal + // The reason is DeepEqual can't determine whether two pointer values are equal by comparing them byte by byte. + // Doing so would report unequal when it shouldn't, + // in the case where two pointers are different but point to the same contents + // (e.g. two *int32s that point to different int32s, + // both of which contain the same value). return typ.PtrBytes == 0 && typ.TFlag&abi.TFlagRegularMemory != 0 } From ff903bb099e89a570ba350ecc5390ad5b12822d8 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Tue, 23 Jan 2024 13:09:25 +0800 Subject: [PATCH 8/8] add test for struct Change-Id: Iba1b710707114ba0b4358b8f01f083e2dca8f68d --- src/reflect/all_test.go | 62 ++++++++++++++++++++++++++++++++++++++++ src/reflect/deepequal.go | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index e4b8ddd224..51d2c70ef0 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -1068,6 +1068,18 @@ var ( p2 = new(slicePtr) ) +type structFloat64 struct { + D float64 +} + +type structFloat32 struct { + D float32 +} + +type structChanInt32 struct { + D chan int32 +} + var deepEqualTests = []DeepEqualTest{ // Equalities {nil, nil, true}, @@ -1150,12 +1162,62 @@ var deepEqualTests = []DeepEqualTest{ {&loopy1, &loopy2, true}, {&cycleMap1, &cycleMap2, true}, {&cycleMap1, &cycleMap3, false}, + + // struct slice and StructOf slice + {[]structFloat64{structFloat64{D: negativeZero[float64]()}}, []structFloat64{structFloat64{D: 0}}, true}, + { + []any{newStructOf(VisibleFields(TypeFor[structFloat64]()), setStructOfField("D", negativeZero[float64]()))}, + []any{newStructOf(VisibleFields(TypeFor[structFloat64]()), setStructOfField("D", float64(0)))}, + true, + }, + + {[]structFloat32{structFloat32{D: negativeZero[float32]()}}, []structFloat32{structFloat32{D: 0}}, true}, + { + []any{newStructOf(VisibleFields(TypeFor[structFloat32]()), setStructOfField("D", negativeZero[float32]()))}, + []any{newStructOf(VisibleFields(TypeFor[structFloat32]()), setStructOfField("D", float32(0)))}, + true, + }, + + {[]structChanInt32{structChanInt32{D: make(chan int32)}}, []structChanInt32{structChanInt32{D: make(chan int32)}}, false}, + { + []any{newStructOf(VisibleFields(TypeFor[structChanInt32]()), setStructOfField("D", make(chan int32)))}, + []any{newStructOf(VisibleFields(TypeFor[structChanInt32]()), setStructOfField("D", make(chan int32)))}, + false, + }, } func ptr[T any](a T) *T { return &a } +type fieldSet func(Value) + +// newStructOf call StructOf get typ +// use typ new any value +// use set change new any value +func newStructOf(field []StructField, set ...fieldSet) (ret any) { + typ := StructOf(field) + rret := New(typ).Elem() + for _, f := range set { + f(rret) + } + return rret.Interface() +} + +// setStructOfField set a field of the struct represented by reflect.Value +func setStructOfField(fieldName string, fieldValue any) func(Value) { + return func(v Value) { + field := v.FieldByName(fieldName) + field.Set(ValueOf(fieldValue)) + } +} + +func negativeZero[T float32 | float64]() T { + var f T + f = -f + return f +} + func TestDeepEqual(t *testing.T) { for _, test := range deepEqualTests { if test.b == (self{}) { diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index 88bc06eda7..05d1d61d39 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -193,7 +193,7 @@ func isDeepEqualRawMemory(typ *abi.Type) (ok bool) { // // The reason is DeepEqual can't determine whether two pointer values are equal by comparing them byte by byte. // Doing so would report unequal when it shouldn't, - // in the case where two pointers are different but point to the same contents + // in the case where two pointers are different but point to the same contents // (e.g. two *int32s that point to different int32s, // both of which contain the same value).