mirror of
https://github.com/golang/go
synced 2024-11-24 07:40:17 -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:
parent
3c29aca436
commit
67d6be139c
@ -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",
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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>"
|
||||
|
Loading…
Reference in New Issue
Block a user