Skip to content

Commit

Permalink
reflect: make more Value methods inlineable
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
dsnet authored and gopherbot committed Apr 21, 2022
1 parent 3c29aca commit 67d6be1
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 18 deletions.
28 changes: 23 additions & 5 deletions src/cmd/compile/internal/test/inl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
90 changes: 90 additions & 0 deletions src/reflect/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
57 changes: 44 additions & 13 deletions src/reflect/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()})
}
Expand Down Expand Up @@ -1580,18 +1602,22 @@ 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)
case Chan:
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
Expand Down Expand Up @@ -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>"
Expand Down

0 comments on commit 67d6be1

Please sign in to comment.