]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: allocate memory in TypeAssert[I] only when the assertion succeeds
authorMateusz Poliwczak <mpoliwczak34@gmail.com>
Thu, 18 Sep 2025 18:41:46 +0000 (20:41 +0200)
committerGopher Robot <gobot@golang.org>
Tue, 23 Sep 2025 18:45:25 +0000 (11:45 -0700)
goos: linux
goarch: amd64
pkg: reflect
cpu: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
                                                                        │ /tmp/before │              /tmp/after               │
                                                                        │   sec/op    │   sec/op     vs base                  │
TypeAssert/TypeAssert[int](int)-8                                         2.599n ± 1%   2.558n ± 0%  -1.56% (p=0.000 n=30)
TypeAssert/TypeAssert[uint8](int)-8                                       2.506n ± 1%   2.579n ± 2%  +2.93% (p=0.008 n=30)
TypeAssert/TypeAssert[fmt.Stringer](reflect_test.testTypeWithMethod)-8    7.449n ± 2%   7.776n ± 2%  +4.39% (p=0.000 n=30)
TypeAssert/TypeAssert[fmt.Stringer](*reflect_test.testTypeWithMethod)-8   7.220n ± 2%   7.439n ± 1%  +3.04% (p=0.000 n=30)
TypeAssert/TypeAssert[interface_{}](int)-8                                4.015n ± 1%   4.207n ± 1%  +4.79% (p=0.000 n=30)
TypeAssert/TypeAssert[interface_{}](reflect_test.testTypeWithMethod)-8    4.003n ± 1%   4.190n ± 2%  +4.66% (p=0.000 n=30)
TypeAssert/TypeAssert[time.Time](time.Time)-8                             2.933n ± 1%   2.942n ± 1%       ~ (p=0.327 n=20+30)
TypeAssert/TypeAssert[func()_string](func()_string)-8                                   111.5n ± 1%
geomean                                                                   4.004n        6.208n       +2.62%

Change-Id: I6a6a6964d6f9c794adc15dc5ff3dc8634b30df89
Reviewed-on: https://go-review.googlesource.com/c/go/+/705255
Reviewed-by: Keith Randall <khr@google.com>
Auto-Submit: Mateusz Poliwczak <mpoliwczak34@gmail.com>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/internal/abi/iface.go
src/reflect/all_test.go
src/reflect/value.go

index 5f3698407d1c1e0f3f5770d8e8652d1d811ba8be..f53d7e1f4f8864452d5dd6ad25ba6ae643b181b4 100644 (file)
@@ -31,3 +31,11 @@ type NonEmptyInterface struct {
        ITab *ITab
        Data unsafe.Pointer
 }
+
+// CommonInterface describes the layout of both [EmptyInterface] and [NonEmptyInterface].
+type CommonInterface struct {
+       // Either an *ITab or a *Type, unexported to avoid accidental use.
+       _ unsafe.Pointer
+
+       Data unsafe.Pointer
+}
index da7b2d77640f571c05a8bbf29377656349fc221b..2a8c5206624146966d5830bc8c6bc6ac0e6ca38b 100644 (file)
@@ -8783,6 +8783,9 @@ func TestTypeAssertAllocs(t *testing.T) {
 
        typeAssertAllocs[time.Time](t, ValueOf(new(time.Time)).Elem(), 0)
        typeAssertAllocs[time.Time](t, ValueOf(*new(time.Time)), 0)
+
+       type I interface{ foo() }
+       typeAssertAllocs[I](t, ValueOf(new(string)).Elem(), 0) // assert fail doesn't alloc
 }
 
 func typeAssertAllocs[T any](t *testing.T, val Value, wantAllocs int) {
index 6f65ef81dc3ae3dea0c989572319c7011c1d4a23..818eacf9cf37f243d9c22f4523ae364607604b1a 100644 (file)
@@ -120,10 +120,16 @@ func (v Value) pointer() unsafe.Pointer {
 
 // packEface converts v to the empty interface.
 func packEface(v Value) any {
+       return *(*any)(unsafe.Pointer(&abi.EmptyInterface{
+               Type: v.typ(),
+               Data: packEfaceData(v),
+       }))
+}
+
+// packEfaceData is a helper that packs the Data part of an interface,
+// if v were to be stored in an interface.
+func packEfaceData(v Value) unsafe.Pointer {
        t := v.typ()
-       // Declare e as a struct (and not pointer to struct) to help escape analysis.
-       e := abi.EmptyInterface{}
-       // First, fill in the data portion of the interface.
        switch {
        case !t.IsDirectIface():
                if v.flag&flagIndir == 0 {
@@ -136,18 +142,15 @@ func packEface(v Value) any {
                        typedmemmove(t, c, ptr)
                        ptr = c
                }
-               e.Data = ptr
+               return ptr
        case v.flag&flagIndir != 0:
                // Value is indirect, but interface is direct. We need
                // to load the data at v.ptr into the interface data word.
-               e.Data = *(*unsafe.Pointer)(v.ptr)
+               return *(*unsafe.Pointer)(v.ptr)
        default:
                // Value is direct, and so is the interface.
-               e.Data = v.ptr
+               return v.ptr
        }
-       // Now, fill in the type portion.
-       e.Type = t
-       return *(*any)(unsafe.Pointer(&e))
 }
 
 // unpackEface converts the empty interface i to a Value.
@@ -1544,8 +1547,18 @@ func TypeAssert[T any](v Value) (T, bool) {
        //      TypeAssert[any](ValueOf(1)) == ValueOf(1).Interface().(any)
        //      TypeAssert[error](ValueOf(&someError{})) == ValueOf(&someError{}).Interface().(error)
        if typ.Kind() == abi.Interface {
-               v, ok := packEface(v).(T)
-               return v, ok
+               // To avoid allocating memory, in case the type assertion fails,
+               // first do the type assertion with a nil Data pointer.
+               iface := *(*any)(unsafe.Pointer(&abi.EmptyInterface{Type: v.typ(), Data: nil}))
+               if out, ok := iface.(T); ok {
+                       // Now populate the Data field properly, we update the Data ptr
+                       // directly to avoid an additional type asertion. We can re-use the
+                       // itab we already got from the runtime (through the previous type assertion).
+                       (*abi.CommonInterface)(unsafe.Pointer(&out)).Data = packEfaceData(v)
+                       return out, true
+               }
+               var zero T
+               return zero, false
        }
 
        // Both v and T must be concrete types.