]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: make more Value methods inlineable
authorJoe Tsai <joetsai@digital-static.net>
Sun, 17 Apr 2022 02:01:48 +0000 (19:01 -0700)
committerGopher Robot <gobot@golang.org>
Thu, 21 Apr 2022 23:45:36 +0000 (23:45 +0000)
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>
src/cmd/compile/internal/test/inl_test.go
src/reflect/all_test.go
src/reflect/value.go

index 211068e1dc3511863a7793514e237954d888ddcf..af66a32085d736ba4de92bc39c9442df9b825734 100644 (file)
@@ -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",
index a886f9f64abbc8b2574825fd07e36241a47b30c3..72d01c7deb10928f2796545c8e6962ceaf0bcfc6 100644 (file)
@@ -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()
+       }
+}
index de24d4c712101227bbbca427134ed53bfcf80c51..6b5ebfae24ca1cab94cb13ecd662e8d2c4d515e4 100644 (file)
@@ -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>"