Skip to content

Commit 67d6be1

Browse files
dsnetgopherbot
authored andcommitted
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 <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 3c29aca commit 67d6be1

File tree

3 files changed

+157
-18
lines changed

3 files changed

+157
-18
lines changed

src/cmd/compile/internal/test/inl_test.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,33 @@ func TestIntendedInlining(t *testing.T) {
128128
"ValidRune",
129129
},
130130
"reflect": {
131-
"Value.CanInt",
132-
"Value.CanUint",
133-
"Value.CanFloat",
134-
"Value.CanComplex",
131+
"Value.Bool",
132+
"Value.Bytes",
135133
"Value.CanAddr",
136-
"Value.CanSet",
134+
"Value.CanComplex",
135+
"Value.CanFloat",
136+
"Value.CanInt",
137137
"Value.CanInterface",
138+
"Value.CanSet",
139+
"Value.CanUint",
140+
"Value.Cap",
141+
"Value.Complex",
142+
"Value.Float",
143+
"Value.Int",
144+
"Value.Interface",
145+
"Value.IsNil",
138146
"Value.IsValid",
147+
"Value.Kind",
148+
"Value.Len",
139149
"Value.MapRange",
150+
"Value.OverflowComplex",
151+
"Value.OverflowFloat",
152+
"Value.OverflowInt",
153+
"Value.OverflowUint",
154+
"Value.String",
155+
"Value.Type",
156+
"Value.Uint",
157+
"Value.UnsafeAddr",
140158
"Value.pointer",
141159
"add",
142160
"align",

src/reflect/all_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7823,3 +7823,93 @@ func TestNegativeKindString(t *testing.T) {
78237823
t.Fatalf("Kind(-1).String() = %q, want %q", s, want)
78247824
}
78257825
}
7826+
7827+
type (
7828+
namedBool bool
7829+
namedBytes []byte
7830+
)
7831+
7832+
var sourceAll = struct {
7833+
Bool Value
7834+
String Value
7835+
Bytes Value
7836+
NamedBytes Value
7837+
BytesArray Value
7838+
SliceAny Value
7839+
MapStringAny Value
7840+
}{
7841+
Bool: ValueOf(new(bool)).Elem(),
7842+
String: ValueOf(new(string)).Elem(),
7843+
Bytes: ValueOf(new([]byte)).Elem(),
7844+
NamedBytes: ValueOf(new(namedBytes)).Elem(),
7845+
BytesArray: ValueOf(new([32]byte)).Elem(),
7846+
SliceAny: ValueOf(new([]any)).Elem(),
7847+
MapStringAny: ValueOf(new(map[string]any)).Elem(),
7848+
}
7849+
7850+
var sinkAll struct {
7851+
RawBool bool
7852+
RawString string
7853+
RawBytes []byte
7854+
RawInt int
7855+
}
7856+
7857+
func BenchmarkBool(b *testing.B) {
7858+
for i := 0; i < b.N; i++ {
7859+
sinkAll.RawBool = sourceAll.Bool.Bool()
7860+
}
7861+
}
7862+
7863+
func BenchmarkString(b *testing.B) {
7864+
for i := 0; i < b.N; i++ {
7865+
sinkAll.RawString = sourceAll.String.String()
7866+
}
7867+
}
7868+
7869+
func BenchmarkBytes(b *testing.B) {
7870+
for i := 0; i < b.N; i++ {
7871+
sinkAll.RawBytes = sourceAll.Bytes.Bytes()
7872+
}
7873+
}
7874+
7875+
func BenchmarkNamedBytes(b *testing.B) {
7876+
for i := 0; i < b.N; i++ {
7877+
sinkAll.RawBytes = sourceAll.NamedBytes.Bytes()
7878+
}
7879+
}
7880+
7881+
func BenchmarkBytesArray(b *testing.B) {
7882+
for i := 0; i < b.N; i++ {
7883+
sinkAll.RawBytes = sourceAll.BytesArray.Bytes()
7884+
}
7885+
}
7886+
7887+
func BenchmarkSliceLen(b *testing.B) {
7888+
for i := 0; i < b.N; i++ {
7889+
sinkAll.RawInt = sourceAll.SliceAny.Len()
7890+
}
7891+
}
7892+
7893+
func BenchmarkMapLen(b *testing.B) {
7894+
for i := 0; i < b.N; i++ {
7895+
sinkAll.RawInt = sourceAll.MapStringAny.Len()
7896+
}
7897+
}
7898+
7899+
func BenchmarkStringLen(b *testing.B) {
7900+
for i := 0; i < b.N; i++ {
7901+
sinkAll.RawInt = sourceAll.String.Len()
7902+
}
7903+
}
7904+
7905+
func BenchmarkArrayLen(b *testing.B) {
7906+
for i := 0; i < b.N; i++ {
7907+
sinkAll.RawInt = sourceAll.BytesArray.Len()
7908+
}
7909+
}
7910+
7911+
func BenchmarkSliceCap(b *testing.B) {
7912+
for i := 0; i < b.N; i++ {
7913+
sinkAll.RawInt = sourceAll.SliceAny.Cap()
7914+
}
7915+
}

src/reflect/value.go

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -281,14 +281,31 @@ func (v Value) Addr() Value {
281281
// Bool returns v's underlying value.
282282
// It panics if v's kind is not Bool.
283283
func (v Value) Bool() bool {
284-
v.mustBe(Bool)
284+
// panicNotBool is split out to keep Bool inlineable.
285+
if v.kind() != Bool {
286+
v.panicNotBool()
287+
}
285288
return *(*bool)(v.ptr)
286289
}
287290

291+
func (v Value) panicNotBool() {
292+
v.mustBe(Bool)
293+
}
294+
295+
var bytesType = TypeOf(([]byte)(nil)).(*rtype)
296+
288297
// Bytes returns v's underlying value.
289298
// It panics if v's underlying value is not a slice of bytes or
290299
// an addressable array of bytes.
291300
func (v Value) Bytes() []byte {
301+
// bytesSlow is split out to keep Bytes inlineable for unnamed []byte.
302+
if v.typ == bytesType {
303+
return *(*[]byte)(v.ptr)
304+
}
305+
return v.bytesSlow()
306+
}
307+
308+
func (v Value) bytesSlow() []byte {
292309
switch v.kind() {
293310
case Slice:
294311
if v.typ.Elem().Kind() != Uint8 {
@@ -1129,15 +1146,20 @@ func funcName(f func([]Value) []Value) string {
11291146
// Cap returns v's capacity.
11301147
// It panics if v's Kind is not Array, Chan, or Slice.
11311148
func (v Value) Cap() int {
1149+
// capNonSlice is split out to keep Cap inlineable for slice kinds.
1150+
if v.kind() == Slice {
1151+
return (*unsafeheader.Slice)(v.ptr).Cap
1152+
}
1153+
return v.capNonSlice()
1154+
}
1155+
1156+
func (v Value) capNonSlice() int {
11321157
k := v.kind()
11331158
switch k {
11341159
case Array:
11351160
return v.typ.Len()
11361161
case Chan:
11371162
return chancap(v.pointer())
1138-
case Slice:
1139-
// Slice is always bigger than a word; assume flagIndir.
1140-
return (*unsafeheader.Slice)(v.ptr).Cap
11411163
}
11421164
panic(&ValueError{"reflect.Value.Cap", v.kind()})
11431165
}
@@ -1580,18 +1602,22 @@ func (v Value) Kind() Kind {
15801602
// Len returns v's length.
15811603
// It panics if v's Kind is not Array, Chan, Map, Slice, or String.
15821604
func (v Value) Len() int {
1583-
k := v.kind()
1584-
switch k {
1605+
// lenNonSlice is split out to keep Len inlineable for slice kinds.
1606+
if v.kind() == Slice {
1607+
return (*unsafeheader.Slice)(v.ptr).Len
1608+
}
1609+
return v.lenNonSlice()
1610+
}
1611+
1612+
func (v Value) lenNonSlice() int {
1613+
switch k := v.kind(); k {
15851614
case Array:
15861615
tt := (*arrayType)(unsafe.Pointer(v.typ))
15871616
return int(tt.len)
15881617
case Chan:
15891618
return chanlen(v.pointer())
15901619
case Map:
15911620
return maplen(v.pointer())
1592-
case Slice:
1593-
// Slice is bigger than a word; assume flagIndir.
1594-
return (*unsafeheader.Slice)(v.ptr).Len
15951621
case String:
15961622
// String is bigger than a word; assume flagIndir.
15971623
return (*unsafeheader.String)(v.ptr).Len
@@ -2441,12 +2467,17 @@ func (v Value) Slice3(i, j, k int) Value {
24412467
// The fmt package treats Values specially. It does not call their String
24422468
// method implicitly but instead prints the concrete values they hold.
24432469
func (v Value) String() string {
2444-
switch k := v.kind(); k {
2445-
case Invalid:
2446-
return "<invalid Value>"
2447-
case String:
2470+
// stringNonString is split out to keep String inlineable for string kinds.
2471+
if v.kind() == String {
24482472
return *(*string)(v.ptr)
24492473
}
2474+
return v.stringNonString()
2475+
}
2476+
2477+
func (v Value) stringNonString() string {
2478+
if v.kind() == Invalid {
2479+
return "<invalid Value>"
2480+
}
24502481
// If you call String on a reflect.Value of other type, it's better to
24512482
// print something than to panic. Useful in debugging.
24522483
return "<" + v.Type().String() + " Value>"

0 commit comments

Comments
 (0)