diff options
author | Joe Tsai <joetsai@digital-static.net> | 2022-04-16 19:01:48 -0700 |
---|---|---|
committer | Gopher Robot <gobot@golang.org> | 2022-04-21 23:45:36 +0000 |
commit | 67d6be139c77b63395d8c1fa29cdae8d34635f3e (patch) | |
tree | 446c84e256f30ff0c09325361e2ecb8e42fc65f6 | |
parent | 3c29aca43630da1604c3378d2351050f23655268 (diff) | |
download | go-67d6be139c77b63395d8c1fa29cdae8d34635f3e.tar.gz go-67d6be139c77b63395d8c1fa29cdae8d34635f3e.zip |
reflect: make more Value methods inlineable
The following Value methods are now inlineable:
Bool for ~bool
String for ~string (but not other kinds)
Bytes for []byte (but not ~[]byte or ~[N]byte)
Len for ~[]T (but not ~[N]T, ~chan T, ~map[K]V, or ~string)
Cap for ~[]T (but not ~[N]T or ~chan T)
For Bytes, we only have enough inline budget to inline one type,
so we optimize for unnamed []byte, which is far more common than
named []byte or [N]byte.
For Len and Cap, we only have enough inline budget to inline one kind,
so we optimize for ~[]T, which is more common than the others.
The exception is string, but the size of a string can be obtained
through len(v.String()).
Performance:
Bool 1.65ns ± 0% 0.51ns ± 3% -68.81% (p=0.008 n=5+5)
String 1.97ns ± 1% 0.70ns ± 1% -64.25% (p=0.008 n=5+5)
Bytes 8.90ns ± 2% 0.89ns ± 1% -89.95% (p=0.008 n=5+5)
NamedBytes 8.89ns ± 1% 8.88ns ± 1% ~ (p=0.548 n=5+5)
BytesArray 10.0ns ± 2% 10.2ns ± 1% +1.58% (p=0.048 n=5+5)
SliceLen 1.97ns ± 1% 0.45ns ± 1% -77.22% (p=0.008 n=5+5)
MapLen 2.62ns ± 1% 3.07ns ± 1% +17.24% (p=0.008 n=5+5)
StringLen 1.96ns ± 1% 1.98ns ± 2% ~ (p=0.151 n=5+5)
ArrayLen 1.96ns ± 1% 2.19ns ± 1% +11.46% (p=0.008 n=5+5)
SliceCap 1.76ns ± 1% 0.45ns ± 2% -74.28% (p=0.008 n=5+5)
There's a slight slowdown (~10-20%) for obtaining the length
of a string or map, but a substantial improvement for slices.
Performance according to encoding/json:
CodeMarshal 555µs ± 2% 562µs ± 4% ~ (p=0.421 n=5+5)
MarshalBytes/32 163ns ± 1% 157ns ± 1% -3.82% (p=0.008 n=5+5)
MarshalBytes/256 453ns ± 1% 447ns ± 1% ~ (p=0.056 n=5+5)
MarshalBytes/4096 4.10µs ± 1% 4.09µs ± 0% ~ (p=1.000 n=5+4)
CodeUnmarshal 3.16ms ± 2% 3.02ms ± 1% -4.18% (p=0.008 n=5+5)
CodeUnmarshalReuse 2.64ms ± 3% 2.51ms ± 2% -4.81% (p=0.016 n=5+5)
UnmarshalString 65.4ns ± 4% 64.1ns ± 0% ~ (p=0.190 n=5+4)
UnmarshalFloat64 59.8ns ± 5% 58.9ns ± 2% ~ (p=0.222 n=5+5)
UnmarshalInt64 51.7ns ± 1% 50.0ns ± 2% -3.26% (p=0.008 n=5+5)
EncodeMarshaler 23.6ns ±11% 20.8ns ± 1% -12.10% (p=0.016 n=5+4)
Add all inlineable methods of Value to cmd/compile/internal/test/inl_test.go.
Change-Id: Ifc192491918af6b62f7fe3a094a5a5256bfb326d
Reviewed-on: https://go-review.googlesource.com/c/go/+/400676
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
-rw-r--r-- | src/cmd/compile/internal/test/inl_test.go | 28 | ||||
-rw-r--r-- | src/reflect/all_test.go | 90 | ||||
-rw-r--r-- | src/reflect/value.go | 57 |
3 files changed, 157 insertions, 18 deletions
diff --git a/src/cmd/compile/internal/test/inl_test.go b/src/cmd/compile/internal/test/inl_test.go index 211068e1dc..af66a32085 100644 --- a/src/cmd/compile/internal/test/inl_test.go +++ b/src/cmd/compile/internal/test/inl_test.go @@ -128,15 +128,33 @@ func TestIntendedInlining(t *testing.T) { "ValidRune", }, "reflect": { - "Value.CanInt", - "Value.CanUint", - "Value.CanFloat", - "Value.CanComplex", + "Value.Bool", + "Value.Bytes", "Value.CanAddr", - "Value.CanSet", + "Value.CanComplex", + "Value.CanFloat", + "Value.CanInt", "Value.CanInterface", + "Value.CanSet", + "Value.CanUint", + "Value.Cap", + "Value.Complex", + "Value.Float", + "Value.Int", + "Value.Interface", + "Value.IsNil", "Value.IsValid", + "Value.Kind", + "Value.Len", "Value.MapRange", + "Value.OverflowComplex", + "Value.OverflowFloat", + "Value.OverflowInt", + "Value.OverflowUint", + "Value.String", + "Value.Type", + "Value.Uint", + "Value.UnsafeAddr", "Value.pointer", "add", "align", diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index a886f9f64a..72d01c7deb 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -7823,3 +7823,93 @@ func TestNegativeKindString(t *testing.T) { t.Fatalf("Kind(-1).String() = %q, want %q", s, want) } } + +type ( + namedBool bool + namedBytes []byte +) + +var sourceAll = struct { + Bool Value + String Value + Bytes Value + NamedBytes Value + BytesArray Value + SliceAny Value + MapStringAny Value +}{ + Bool: ValueOf(new(bool)).Elem(), + String: ValueOf(new(string)).Elem(), + Bytes: ValueOf(new([]byte)).Elem(), + NamedBytes: ValueOf(new(namedBytes)).Elem(), + BytesArray: ValueOf(new([32]byte)).Elem(), + SliceAny: ValueOf(new([]any)).Elem(), + MapStringAny: ValueOf(new(map[string]any)).Elem(), +} + +var sinkAll struct { + RawBool bool + RawString string + RawBytes []byte + RawInt int +} + +func BenchmarkBool(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkAll.RawBool = sourceAll.Bool.Bool() + } +} + +func BenchmarkString(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkAll.RawString = sourceAll.String.String() + } +} + +func BenchmarkBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkAll.RawBytes = sourceAll.Bytes.Bytes() + } +} + +func BenchmarkNamedBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkAll.RawBytes = sourceAll.NamedBytes.Bytes() + } +} + +func BenchmarkBytesArray(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkAll.RawBytes = sourceAll.BytesArray.Bytes() + } +} + +func BenchmarkSliceLen(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkAll.RawInt = sourceAll.SliceAny.Len() + } +} + +func BenchmarkMapLen(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkAll.RawInt = sourceAll.MapStringAny.Len() + } +} + +func BenchmarkStringLen(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkAll.RawInt = sourceAll.String.Len() + } +} + +func BenchmarkArrayLen(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkAll.RawInt = sourceAll.BytesArray.Len() + } +} + +func BenchmarkSliceCap(b *testing.B) { + for i := 0; i < b.N; i++ { + sinkAll.RawInt = sourceAll.SliceAny.Cap() + } +} diff --git a/src/reflect/value.go b/src/reflect/value.go index de24d4c712..6b5ebfae24 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -281,14 +281,31 @@ func (v Value) Addr() Value { // Bool returns v's underlying value. // It panics if v's kind is not Bool. func (v Value) Bool() bool { - v.mustBe(Bool) + // panicNotBool is split out to keep Bool inlineable. + if v.kind() != Bool { + v.panicNotBool() + } return *(*bool)(v.ptr) } +func (v Value) panicNotBool() { + v.mustBe(Bool) +} + +var bytesType = TypeOf(([]byte)(nil)).(*rtype) + // Bytes returns v's underlying value. // It panics if v's underlying value is not a slice of bytes or // an addressable array of bytes. func (v Value) Bytes() []byte { + // bytesSlow is split out to keep Bytes inlineable for unnamed []byte. + if v.typ == bytesType { + return *(*[]byte)(v.ptr) + } + return v.bytesSlow() +} + +func (v Value) bytesSlow() []byte { switch v.kind() { case Slice: if v.typ.Elem().Kind() != Uint8 { @@ -1129,15 +1146,20 @@ func funcName(f func([]Value) []Value) string { // Cap returns v's capacity. // It panics if v's Kind is not Array, Chan, or Slice. func (v Value) Cap() int { + // capNonSlice is split out to keep Cap inlineable for slice kinds. + if v.kind() == Slice { + return (*unsafeheader.Slice)(v.ptr).Cap + } + return v.capNonSlice() +} + +func (v Value) capNonSlice() int { k := v.kind() switch k { case Array: return v.typ.Len() case Chan: return chancap(v.pointer()) - case Slice: - // Slice is always bigger than a word; assume flagIndir. - return (*unsafeheader.Slice)(v.ptr).Cap } panic(&ValueError{"reflect.Value.Cap", v.kind()}) } @@ -1580,8 +1602,15 @@ func (v Value) Kind() Kind { // Len returns v's length. // It panics if v's Kind is not Array, Chan, Map, Slice, or String. func (v Value) Len() int { - k := v.kind() - switch k { + // lenNonSlice is split out to keep Len inlineable for slice kinds. + if v.kind() == Slice { + return (*unsafeheader.Slice)(v.ptr).Len + } + return v.lenNonSlice() +} + +func (v Value) lenNonSlice() int { + switch k := v.kind(); k { case Array: tt := (*arrayType)(unsafe.Pointer(v.typ)) return int(tt.len) @@ -1589,9 +1618,6 @@ func (v Value) Len() int { return chanlen(v.pointer()) case Map: return maplen(v.pointer()) - case Slice: - // Slice is bigger than a word; assume flagIndir. - return (*unsafeheader.Slice)(v.ptr).Len case String: // String is bigger than a word; assume flagIndir. return (*unsafeheader.String)(v.ptr).Len @@ -2441,12 +2467,17 @@ func (v Value) Slice3(i, j, k int) Value { // The fmt package treats Values specially. It does not call their String // method implicitly but instead prints the concrete values they hold. func (v Value) String() string { - switch k := v.kind(); k { - case Invalid: - return "<invalid Value>" - case String: + // stringNonString is split out to keep String inlineable for string kinds. + if v.kind() == String { return *(*string)(v.ptr) } + return v.stringNonString() +} + +func (v Value) stringNonString() string { + if v.kind() == Invalid { + return "<invalid Value>" + } // If you call String on a reflect.Value of other type, it's better to // print something than to panic. Useful in debugging. return "<" + v.Type().String() + " Value>" |