From ada5c2edb4c2d3db988805b822e58020e6cd5f49 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Sun, 19 Nov 2023 11:22:05 +0000 Subject: [PATCH] reflect: optimize Value.IsZero for struct types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For some types where the zero value is a value where all bits of this type are 0 optimize it. goos: windows goarch: amd64 pkg: reflect cpu: AMD Ryzen 7 7840HS w/ Radeon 780M Graphics │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ IsZero/StructInt_512-16 109.75n ± 0% 72.61n ± 1% -33.84% (p=0.000 n=12) Change-Id: I56de8b95f4d4482068960d6f38938763fa1caa90 GitHub-Last-Rev: c143f0cd7616cb3be52c59879f748e49a3c5cbf1 GitHub-Pull-Request: golang/go#64220 Reviewed-on: https://go-review.googlesource.com/c/go/+/543355 Reviewed-by: David Chase Reviewed-by: Cherry Mui TryBot-Result: Gopher Robot Auto-Submit: Keith Randall Reviewed-by: Keith Randall --- src/reflect/all_test.go | 8 ++++++- src/reflect/benchmark_test.go | 4 ++++ src/reflect/value.go | 39 +++++++++++++++++++++++++---------- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 71969106e4..a28f2a4bed 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -1500,6 +1500,12 @@ func TestIsZero(t *testing.T) { {setField(struct{ _, a, _ func() }{}, 0*unsafe.Sizeof((func())(nil)), func() {}), true}, {setField(struct{ _, a, _ func() }{}, 1*unsafe.Sizeof((func())(nil)), func() {}), false}, {setField(struct{ _, a, _ func() }{}, 2*unsafe.Sizeof((func())(nil)), func() {}), true}, + {struct{ a [256]S }{}, true}, + {struct{ a [256]S }{a: [256]S{2: {i1: 1}}}, false}, + {struct{ a [256]float32 }{}, true}, + {struct{ a [256]float32 }{a: [256]float32{2: 1.0}}, false}, + {struct{ _, a [256]S }{}, true}, + {setField(struct{ _, a [256]S }{}, 0*unsafe.Sizeof(int64(0)), int64(1)), true}, // UnsafePointer {(unsafe.Pointer)(nil), true}, {(unsafe.Pointer)(new(int)), false}, @@ -1541,7 +1547,7 @@ func TestIsZero(t *testing.T) { func TestInternalIsZero(t *testing.T) { b := make([]byte, 512) for a := 0; a < 8; a++ { - for i := 256 + 7; i <= 512-a; i++ { + for i := 1; i <= 512-a; i++ { InternalIsZero(b[a : a+i]) } } diff --git a/src/reflect/benchmark_test.go b/src/reflect/benchmark_test.go index 4aa47669a2..2e701b062e 100644 --- a/src/reflect/benchmark_test.go +++ b/src/reflect/benchmark_test.go @@ -126,6 +126,9 @@ func BenchmarkIsZero(b *testing.B) { type Int1024 struct { a [1024]int } + type Int512 struct { + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16 [16]S + } s := struct { ArrayComparable [4]T ArrayIncomparable [4]_Complex @@ -137,6 +140,7 @@ func BenchmarkIsZero(b *testing.B) { Struct4Int Int4 ArrayStruct4Int_1024 [256]Int4 ArrayChanInt_1024 [1024]chan int + StructInt_512 Int512 }{} s.ArrayInt_1024_NoZero[512] = 1 source := ValueOf(s) diff --git a/src/reflect/value.go b/src/reflect/value.go index 5bfdb55fd9..2bd41f37fd 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1627,13 +1627,19 @@ func (v Value) IsZero() bool { case String: return v.Len() == 0 case Struct: + if v.flag&flagIndir == 0 { + return v.ptr == nil + } + typ := (*abi.StructType)(unsafe.Pointer(v.typ())) // 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 - } + if typ.Equal != nil && typ.Size() <= maxZero { // See noescape justification above. - return v.typ().Equal(noescape(v.ptr), unsafe.Pointer(&zeroVal[0])) + return typ.Equal(noescape(v.ptr), unsafe.Pointer(&zeroVal[0])) + } + if typ.TFlag&abi.TFlagRegularMemory != 0 { + // For some types where the zero value is a value where all bits of this type are 0 + // optimize it. + return isZero(unsafe.Slice(((*byte)(v.ptr)), typ.Size())) } n := v.NumField() @@ -1650,28 +1656,39 @@ func (v Value) IsZero() bool { } } -// isZero must have len(b)>256+7 to ensure at -// least one 8-byte aligned [256]byte, -// otherwise the access will be out of bounds. -// For all zeros, performance is not as good as +// isZero For all zeros, performance is not as good as // return bytealg.Count(b, byte(0)) == len(b) func isZero(b []byte) bool { + if len(b) == 0 { + return true + } const n = 32 - const bit = n * 8 // Align memory addresses to 8 bytes for uintptr(unsafe.Pointer(&b[0]))%8 != 0 { if b[0] != 0 { return false } b = b[1:] + if len(b) == 0 { + return true + } } - for len(b)%bit != 0 { + for len(b)%8 != 0 { if b[len(b)-1] != 0 { return false } b = b[:len(b)-1] } + if len(b) == 0 { + return true + } w := unsafe.Slice((*uint64)(unsafe.Pointer(&b[0])), len(b)/8) + for len(w)%n != 0 { + if w[0] != 0 { + return false + } + w = w[1:] + } for len(w) >= n { if w[0] != 0 || w[1] != 0 || w[2] != 0 || w[3] != 0 || w[4] != 0 || w[5] != 0 || w[6] != 0 || w[7] != 0 || w[8] != 0 || w[9] != 0 || w[10] != 0 || w[11] != 0 || w[12] != 0 || w[13] != 0 || w[14] != 0 || w[15] != 0 || w[16] != 0 || w[17] != 0 || w[18] != 0 || w[19] != 0 || w[20] != 0 || w[21] != 0 || w[22] != 0 || w[23] != 0 || w[24] != 0 || w[25] != 0 || w[26] != 0 || w[27] != 0 || w[28] != 0 || w[29] != 0 || w[30] != 0 || w[31] != 0 { return false