reflect: allocate memory in TypeAssert[I] only when the assertion succeeds

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>
This commit is contained in:
Mateusz Poliwczak 2025-09-18 20:41:46 +02:00 committed by Gopher Robot
parent a5866ebe40
commit 684e8d3363
3 changed files with 35 additions and 11 deletions

View file

@ -31,3 +31,11 @@ type NonEmptyInterface struct {
ITab *ITab ITab *ITab
Data unsafe.Pointer 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
}

View 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)).Elem(), 0)
typeAssertAllocs[time.Time](t, ValueOf(*new(time.Time)), 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) { func typeAssertAllocs[T any](t *testing.T, val Value, wantAllocs int) {

View file

@ -120,10 +120,16 @@ func (v Value) pointer() unsafe.Pointer {
// packEface converts v to the empty interface. // packEface converts v to the empty interface.
func packEface(v Value) any { 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() 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 { switch {
case !t.IsDirectIface(): case !t.IsDirectIface():
if v.flag&flagIndir == 0 { if v.flag&flagIndir == 0 {
@ -136,18 +142,15 @@ func packEface(v Value) any {
typedmemmove(t, c, ptr) typedmemmove(t, c, ptr)
ptr = c ptr = c
} }
e.Data = ptr return ptr
case v.flag&flagIndir != 0: case v.flag&flagIndir != 0:
// Value is indirect, but interface is direct. We need // Value is indirect, but interface is direct. We need
// to load the data at v.ptr into the interface data word. // to load the data at v.ptr into the interface data word.
e.Data = *(*unsafe.Pointer)(v.ptr) return *(*unsafe.Pointer)(v.ptr)
default: default:
// Value is direct, and so is the interface. // 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. // 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[any](ValueOf(1)) == ValueOf(1).Interface().(any)
// TypeAssert[error](ValueOf(&someError{})) == ValueOf(&someError{}).Interface().(error) // TypeAssert[error](ValueOf(&someError{})) == ValueOf(&someError{}).Interface().(error)
if typ.Kind() == abi.Interface { if typ.Kind() == abi.Interface {
v, ok := packEface(v).(T) // To avoid allocating memory, in case the type assertion fails,
return v, ok // 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. // Both v and T must be concrete types.