1
0
mirror of https://github.com/golang/go synced 2024-11-24 11:00:08 -07:00

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>
This commit is contained in:
Joe Tsai 2022-04-16 19:01:48 -07:00 committed by Gopher Robot
parent 3c29aca436
commit 67d6be139c
3 changed files with 157 additions and 18 deletions

View File

@ -128,15 +128,33 @@ func TestIntendedInlining(t *testing.T) {
"ValidRune", "ValidRune",
}, },
"reflect": { "reflect": {
"Value.CanInt", "Value.Bool",
"Value.CanUint", "Value.Bytes",
"Value.CanFloat",
"Value.CanComplex",
"Value.CanAddr", "Value.CanAddr",
"Value.CanSet", "Value.CanComplex",
"Value.CanFloat",
"Value.CanInt",
"Value.CanInterface", "Value.CanInterface",
"Value.CanSet",
"Value.CanUint",
"Value.Cap",
"Value.Complex",
"Value.Float",
"Value.Int",
"Value.Interface",
"Value.IsNil",
"Value.IsValid", "Value.IsValid",
"Value.Kind",
"Value.Len",
"Value.MapRange", "Value.MapRange",
"Value.OverflowComplex",
"Value.OverflowFloat",
"Value.OverflowInt",
"Value.OverflowUint",
"Value.String",
"Value.Type",
"Value.Uint",
"Value.UnsafeAddr",
"Value.pointer", "Value.pointer",
"add", "add",
"align", "align",

View File

@ -7823,3 +7823,93 @@ func TestNegativeKindString(t *testing.T) {
t.Fatalf("Kind(-1).String() = %q, want %q", s, want) 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()
}
}

View File

@ -281,14 +281,31 @@ func (v Value) Addr() Value {
// Bool returns v's underlying value. // Bool returns v's underlying value.
// It panics if v's kind is not Bool. // It panics if v's kind is not Bool.
func (v Value) Bool() 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) return *(*bool)(v.ptr)
} }
func (v Value) panicNotBool() {
v.mustBe(Bool)
}
var bytesType = TypeOf(([]byte)(nil)).(*rtype)
// Bytes returns v's underlying value. // Bytes returns v's underlying value.
// It panics if v's underlying value is not a slice of bytes or // It panics if v's underlying value is not a slice of bytes or
// an addressable array of bytes. // an addressable array of bytes.
func (v Value) Bytes() []byte { 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() { switch v.kind() {
case Slice: case Slice:
if v.typ.Elem().Kind() != Uint8 { if v.typ.Elem().Kind() != Uint8 {
@ -1129,15 +1146,20 @@ func funcName(f func([]Value) []Value) string {
// Cap returns v's capacity. // Cap returns v's capacity.
// It panics if v's Kind is not Array, Chan, or Slice. // It panics if v's Kind is not Array, Chan, or Slice.
func (v Value) Cap() int { 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() k := v.kind()
switch k { switch k {
case Array: case Array:
return v.typ.Len() return v.typ.Len()
case Chan: case Chan:
return chancap(v.pointer()) 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()}) panic(&ValueError{"reflect.Value.Cap", v.kind()})
} }
@ -1580,8 +1602,15 @@ func (v Value) Kind() Kind {
// Len returns v's length. // Len returns v's length.
// It panics if v's Kind is not Array, Chan, Map, Slice, or String. // It panics if v's Kind is not Array, Chan, Map, Slice, or String.
func (v Value) Len() int { func (v Value) Len() int {
k := v.kind() // lenNonSlice is split out to keep Len inlineable for slice kinds.
switch k { 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: case Array:
tt := (*arrayType)(unsafe.Pointer(v.typ)) tt := (*arrayType)(unsafe.Pointer(v.typ))
return int(tt.len) return int(tt.len)
@ -1589,9 +1618,6 @@ func (v Value) Len() int {
return chanlen(v.pointer()) return chanlen(v.pointer())
case Map: case Map:
return maplen(v.pointer()) return maplen(v.pointer())
case Slice:
// Slice is bigger than a word; assume flagIndir.
return (*unsafeheader.Slice)(v.ptr).Len
case String: case String:
// String is bigger than a word; assume flagIndir. // String is bigger than a word; assume flagIndir.
return (*unsafeheader.String)(v.ptr).Len 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 // The fmt package treats Values specially. It does not call their String
// method implicitly but instead prints the concrete values they hold. // method implicitly but instead prints the concrete values they hold.
func (v Value) String() string { func (v Value) String() string {
switch k := v.kind(); k { // stringNonString is split out to keep String inlineable for string kinds.
case Invalid: if v.kind() == String {
return "<invalid Value>"
case String:
return *(*string)(v.ptr) 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 // If you call String on a reflect.Value of other type, it's better to
// print something than to panic. Useful in debugging. // print something than to panic. Useful in debugging.
return "<" + v.Type().String() + " Value>" return "<" + v.Type().String() + " Value>"