reflect: allocate hiter as part of MapIter

This reduces the number of allocations per
reflect map iteration from two to one.

For #46293

Change-Id: Ibcff5f42fc512e637b6e460bad4518e7ac83d4c3
Reviewed-on: https://go-review.googlesource.com/c/go/+/321889
Trust: Josh Bleecher Snyder <josharian@gmail.com>
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
Josh Bleecher Snyder 2021-05-20 09:57:04 -07:00
parent 9133245be7
commit 1b2d794ca3
3 changed files with 56 additions and 34 deletions

View File

@ -370,10 +370,9 @@ func TestMapIterSet(t *testing.T) {
iter.SetValue(e) iter.SetValue(e)
} }
})) }))
// Making a *MapIter and making an hiter both allocate. // Making a *MapIter allocates. This should be the only allocation.
// Those should be the only two allocations. if got != 1 {
if got != 2 { t.Errorf("wanted 1 alloc, got %d", got)
t.Errorf("wanted 2 allocs, got %d", got)
} }
} }

View File

@ -1549,11 +1549,12 @@ func (v Value) MapKeys() []Value {
if m != nil { if m != nil {
mlen = maplen(m) mlen = maplen(m)
} }
it := mapiterinit(v.typ, m) var it hiter
mapiterinit(v.typ, m, &it)
a := make([]Value, mlen) a := make([]Value, mlen)
var i int var i int
for i = 0; i < len(a); i++ { for i = 0; i < len(a); i++ {
key := mapiterkey(it) key := mapiterkey(&it)
if key == nil { if key == nil {
// Someone deleted an entry from the map since we // Someone deleted an entry from the map since we
// called maplen above. It's a data race, but nothing // called maplen above. It's a data race, but nothing
@ -1561,24 +1562,50 @@ func (v Value) MapKeys() []Value {
break break
} }
a[i] = copyVal(keyType, fl, key) a[i] = copyVal(keyType, fl, key)
mapiternext(it) mapiternext(&it)
} }
return a[:i] return a[:i]
} }
// hiter's structure matches runtime.hiter's structure.
// Having a clone here allows us to embed a map iterator
// inside type MapIter so that MapIters can be re-used
// without doing any allocations.
type hiter struct {
key unsafe.Pointer
elem unsafe.Pointer
t unsafe.Pointer
h unsafe.Pointer
buckets unsafe.Pointer
bptr unsafe.Pointer
overflow *[]unsafe.Pointer
oldoverflow *[]unsafe.Pointer
startBucket uintptr
offset uint8
wrapped bool
B uint8
i uint8
bucket uintptr
checkBucket uintptr
}
func (h hiter) initialized() bool {
return h.t != nil
}
// A MapIter is an iterator for ranging over a map. // A MapIter is an iterator for ranging over a map.
// See Value.MapRange. // See Value.MapRange.
type MapIter struct { type MapIter struct {
m Value m Value
it unsafe.Pointer hiter hiter
} }
// Key returns the key of the iterator's current map entry. // Key returns the key of the iterator's current map entry.
func (it *MapIter) Key() Value { func (it *MapIter) Key() Value {
if it.it == nil { if !it.hiter.initialized() {
panic("MapIter.Key called before Next") panic("MapIter.Key called before Next")
} }
iterkey := mapiterkey(it.it) iterkey := mapiterkey(&it.hiter)
if iterkey == nil { if iterkey == nil {
panic("MapIter.Key called on exhausted iterator") panic("MapIter.Key called on exhausted iterator")
} }
@ -1592,10 +1619,10 @@ func (it *MapIter) Key() Value {
// It is equivalent to dst.Set(it.Key()), but it avoids allocating a new Value. // It is equivalent to dst.Set(it.Key()), but it avoids allocating a new Value.
// As in Go, the key must be assignable to dst's type. // As in Go, the key must be assignable to dst's type.
func (it *MapIter) SetKey(dst Value) { func (it *MapIter) SetKey(dst Value) {
if it.it == nil { if !it.hiter.initialized() {
panic("MapIter.SetKey called before Next") panic("MapIter.SetKey called before Next")
} }
iterkey := mapiterkey(it.it) iterkey := mapiterkey(&it.hiter)
if iterkey == nil { if iterkey == nil {
panic("MapIter.SetKey called on exhausted iterator") panic("MapIter.SetKey called on exhausted iterator")
} }
@ -1616,10 +1643,10 @@ func (it *MapIter) SetKey(dst Value) {
// Value returns the value of the iterator's current map entry. // Value returns the value of the iterator's current map entry.
func (it *MapIter) Value() Value { func (it *MapIter) Value() Value {
if it.it == nil { if !it.hiter.initialized() {
panic("MapIter.Value called before Next") panic("MapIter.Value called before Next")
} }
iterelem := mapiterelem(it.it) iterelem := mapiterelem(&it.hiter)
if iterelem == nil { if iterelem == nil {
panic("MapIter.Value called on exhausted iterator") panic("MapIter.Value called on exhausted iterator")
} }
@ -1633,10 +1660,10 @@ func (it *MapIter) Value() Value {
// It is equivalent to dst.Set(it.Value()), but it avoids allocating a new Value. // It is equivalent to dst.Set(it.Value()), but it avoids allocating a new Value.
// As in Go, the value must be assignable to dst's type. // As in Go, the value must be assignable to dst's type.
func (it *MapIter) SetValue(dst Value) { func (it *MapIter) SetValue(dst Value) {
if it.it == nil { if !it.hiter.initialized() {
panic("MapIter.SetValue called before Next") panic("MapIter.SetValue called before Next")
} }
iterelem := mapiterelem(it.it) iterelem := mapiterelem(&it.hiter)
if iterelem == nil { if iterelem == nil {
panic("MapIter.SetValue called on exhausted iterator") panic("MapIter.SetValue called on exhausted iterator")
} }
@ -1659,15 +1686,15 @@ func (it *MapIter) SetValue(dst Value) {
// entry. It returns false when the iterator is exhausted; subsequent // entry. It returns false when the iterator is exhausted; subsequent
// calls to Key, Value, or Next will panic. // calls to Key, Value, or Next will panic.
func (it *MapIter) Next() bool { func (it *MapIter) Next() bool {
if it.it == nil { if !it.hiter.initialized() {
it.it = mapiterinit(it.m.typ, it.m.pointer()) mapiterinit(it.m.typ, it.m.pointer(), &it.hiter)
} else { } else {
if mapiterkey(it.it) == nil { if mapiterkey(&it.hiter) == nil {
panic("MapIter.Next called on exhausted iterator") panic("MapIter.Next called on exhausted iterator")
} }
mapiternext(it.it) mapiternext(&it.hiter)
} }
return mapiterkey(it.it) != nil return mapiterkey(&it.hiter) != nil
} }
// MapRange returns a range iterator for a map. // MapRange returns a range iterator for a map.
@ -3216,19 +3243,17 @@ func mapassign(t *rtype, m unsafe.Pointer, key, val unsafe.Pointer)
//go:noescape //go:noescape
func mapdelete(t *rtype, m unsafe.Pointer, key unsafe.Pointer) func mapdelete(t *rtype, m unsafe.Pointer, key unsafe.Pointer)
// m escapes into the return value, but the caller of mapiterinit
// doesn't let the return value escape.
//go:noescape //go:noescape
func mapiterinit(t *rtype, m unsafe.Pointer) unsafe.Pointer func mapiterinit(t *rtype, m unsafe.Pointer, it *hiter)
//go:noescape //go:noescape
func mapiterkey(it unsafe.Pointer) (key unsafe.Pointer) func mapiterkey(it *hiter) (key unsafe.Pointer)
//go:noescape //go:noescape
func mapiterelem(it unsafe.Pointer) (elem unsafe.Pointer) func mapiterelem(it *hiter) (elem unsafe.Pointer)
//go:noescape //go:noescape
func mapiternext(it unsafe.Pointer) func mapiternext(it *hiter)
//go:noescape //go:noescape
func maplen(m unsafe.Pointer) int func maplen(m unsafe.Pointer) int

View File

@ -160,8 +160,8 @@ type bmap struct {
} }
// A hash iteration structure. // A hash iteration structure.
// If you modify hiter, also change cmd/compile/internal/reflectdata/reflect.go to indicate // If you modify hiter, also change cmd/compile/internal/reflectdata/reflect.go
// the layout of this structure. // and reflect/value.go to match the layout of this structure.
type hiter struct { type hiter struct {
key unsafe.Pointer // Must be in first position. Write nil to indicate iteration end (see cmd/compile/internal/walk/range.go). key unsafe.Pointer // Must be in first position. Write nil to indicate iteration end (see cmd/compile/internal/walk/range.go).
elem unsafe.Pointer // Must be in second position (see cmd/compile/internal/walk/range.go). elem unsafe.Pointer // Must be in second position (see cmd/compile/internal/walk/range.go).
@ -806,6 +806,7 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapiterinit)) racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapiterinit))
} }
it.t = t
if h == nil || h.count == 0 { if h == nil || h.count == 0 {
return return
} }
@ -813,7 +814,6 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
if unsafe.Sizeof(hiter{})/goarch.PtrSize != 12 { if unsafe.Sizeof(hiter{})/goarch.PtrSize != 12 {
throw("hash_iter size incorrect") // see cmd/compile/internal/reflectdata/reflect.go throw("hash_iter size incorrect") // see cmd/compile/internal/reflectdata/reflect.go
} }
it.t = t
it.h = h it.h = h
// grab snapshot of bucket state // grab snapshot of bucket state
@ -1336,10 +1336,8 @@ func reflect_mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
} }
//go:linkname reflect_mapiterinit reflect.mapiterinit //go:linkname reflect_mapiterinit reflect.mapiterinit
func reflect_mapiterinit(t *maptype, h *hmap) *hiter { func reflect_mapiterinit(t *maptype, h *hmap, it *hiter) {
it := new(hiter)
mapiterinit(t, h, it) mapiterinit(t, h, it)
return it
} }
//go:linkname reflect_mapiternext reflect.mapiternext //go:linkname reflect_mapiternext reflect.mapiternext