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>
diff --git a/src/cmd/compile/internal/test/inl_test.go b/src/cmd/compile/internal/test/inl_test.go
index 211068e..af66a32 100644
--- a/src/cmd/compile/internal/test/inl_test.go
+++ b/src/cmd/compile/internal/test/inl_test.go
@@ -128,15 +128,33 @@
"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 a886f9f..72d01c7 100644
--- a/src/reflect/all_test.go
+++ b/src/reflect/all_test.go
@@ -7823,3 +7823,93 @@
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 de24d4c..6b5ebfa 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -281,14 +281,31 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
// 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>"