From: Mateusz Poliwczak Date: Thu, 18 Sep 2025 18:41:46 +0000 (+0200) Subject: reflect: allocate memory in TypeAssert[I] only when the assertion succeeds X-Git-Tag: go1.26rc1~807 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=684e8d3363;p=gostls13.git 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 Auto-Submit: Mateusz Poliwczak Reviewed-by: Keith Randall Reviewed-by: Junyang Shao LUCI-TryBot-Result: Go LUCI --- diff --git a/src/internal/abi/iface.go b/src/internal/abi/iface.go index 5f3698407d..f53d7e1f4f 100644 --- a/src/internal/abi/iface.go +++ b/src/internal/abi/iface.go @@ -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 +} diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index da7b2d7764..2a8c520662 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -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) { diff --git a/src/reflect/value.go b/src/reflect/value.go index 6f65ef81dc..818eacf9cf 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -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.