Skip to content

Commit 684e8d3

Browse files
mateusz834gopherbot
authored andcommitted
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 <[email protected]> Auto-Submit: Mateusz Poliwczak <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Junyang Shao <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent a5866eb commit 684e8d3

File tree

3 files changed

+35
-11
lines changed

3 files changed

+35
-11
lines changed

src/internal/abi/iface.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,11 @@ type NonEmptyInterface struct {
3131
ITab *ITab
3232
Data unsafe.Pointer
3333
}
34+
35+
// CommonInterface describes the layout of both [EmptyInterface] and [NonEmptyInterface].
36+
type CommonInterface struct {
37+
// Either an *ITab or a *Type, unexported to avoid accidental use.
38+
_ unsafe.Pointer
39+
40+
Data unsafe.Pointer
41+
}

src/reflect/all_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8783,6 +8783,9 @@ func TestTypeAssertAllocs(t *testing.T) {
87838783

87848784
typeAssertAllocs[time.Time](t, ValueOf(new(time.Time)).Elem(), 0)
87858785
typeAssertAllocs[time.Time](t, ValueOf(*new(time.Time)), 0)
8786+
8787+
type I interface{ foo() }
8788+
typeAssertAllocs[I](t, ValueOf(new(string)).Elem(), 0) // assert fail doesn't alloc
87868789
}
87878790

87888791
func typeAssertAllocs[T any](t *testing.T, val Value, wantAllocs int) {

src/reflect/value.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,16 @@ func (v Value) pointer() unsafe.Pointer {
120120

121121
// packEface converts v to the empty interface.
122122
func packEface(v Value) any {
123+
return *(*any)(unsafe.Pointer(&abi.EmptyInterface{
124+
Type: v.typ(),
125+
Data: packEfaceData(v),
126+
}))
127+
}
128+
129+
// packEfaceData is a helper that packs the Data part of an interface,
130+
// if v were to be stored in an interface.
131+
func packEfaceData(v Value) unsafe.Pointer {
123132
t := v.typ()
124-
// Declare e as a struct (and not pointer to struct) to help escape analysis.
125-
e := abi.EmptyInterface{}
126-
// First, fill in the data portion of the interface.
127133
switch {
128134
case !t.IsDirectIface():
129135
if v.flag&flagIndir == 0 {
@@ -136,18 +142,15 @@ func packEface(v Value) any {
136142
typedmemmove(t, c, ptr)
137143
ptr = c
138144
}
139-
e.Data = ptr
145+
return ptr
140146
case v.flag&flagIndir != 0:
141147
// Value is indirect, but interface is direct. We need
142148
// to load the data at v.ptr into the interface data word.
143-
e.Data = *(*unsafe.Pointer)(v.ptr)
149+
return *(*unsafe.Pointer)(v.ptr)
144150
default:
145151
// Value is direct, and so is the interface.
146-
e.Data = v.ptr
152+
return v.ptr
147153
}
148-
// Now, fill in the type portion.
149-
e.Type = t
150-
return *(*any)(unsafe.Pointer(&e))
151154
}
152155

153156
// unpackEface converts the empty interface i to a Value.
@@ -1544,8 +1547,18 @@ func TypeAssert[T any](v Value) (T, bool) {
15441547
// TypeAssert[any](ValueOf(1)) == ValueOf(1).Interface().(any)
15451548
// TypeAssert[error](ValueOf(&someError{})) == ValueOf(&someError{}).Interface().(error)
15461549
if typ.Kind() == abi.Interface {
1547-
v, ok := packEface(v).(T)
1548-
return v, ok
1550+
// To avoid allocating memory, in case the type assertion fails,
1551+
// first do the type assertion with a nil Data pointer.
1552+
iface := *(*any)(unsafe.Pointer(&abi.EmptyInterface{Type: v.typ(), Data: nil}))
1553+
if out, ok := iface.(T); ok {
1554+
// Now populate the Data field properly, we update the Data ptr
1555+
// directly to avoid an additional type asertion. We can re-use the
1556+
// itab we already got from the runtime (through the previous type assertion).
1557+
(*abi.CommonInterface)(unsafe.Pointer(&out)).Data = packEfaceData(v)
1558+
return out, true
1559+
}
1560+
var zero T
1561+
return zero, false
15491562
}
15501563

15511564
// Both v and T must be concrete types.

0 commit comments

Comments
 (0)