Skip to content

Commit 1b2d794

Browse files
committed
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 <[email protected]> Run-TryBot: Josh Bleecher Snyder <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 9133245 commit 1b2d794

File tree

3 files changed

+56
-34
lines changed

3 files changed

+56
-34
lines changed

src/reflect/all_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -370,10 +370,9 @@ func TestMapIterSet(t *testing.T) {
370370
iter.SetValue(e)
371371
}
372372
}))
373-
// Making a *MapIter and making an hiter both allocate.
374-
// Those should be the only two allocations.
375-
if got != 2 {
376-
t.Errorf("wanted 2 allocs, got %d", got)
373+
// Making a *MapIter allocates. This should be the only allocation.
374+
if got != 1 {
375+
t.Errorf("wanted 1 alloc, got %d", got)
377376
}
378377
}
379378

src/reflect/value.go

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,36 +1549,63 @@ func (v Value) MapKeys() []Value {
15491549
if m != nil {
15501550
mlen = maplen(m)
15511551
}
1552-
it := mapiterinit(v.typ, m)
1552+
var it hiter
1553+
mapiterinit(v.typ, m, &it)
15531554
a := make([]Value, mlen)
15541555
var i int
15551556
for i = 0; i < len(a); i++ {
1556-
key := mapiterkey(it)
1557+
key := mapiterkey(&it)
15571558
if key == nil {
15581559
// Someone deleted an entry from the map since we
15591560
// called maplen above. It's a data race, but nothing
15601561
// we can do about it.
15611562
break
15621563
}
15631564
a[i] = copyVal(keyType, fl, key)
1564-
mapiternext(it)
1565+
mapiternext(&it)
15651566
}
15661567
return a[:i]
15671568
}
15681569

1570+
// hiter's structure matches runtime.hiter's structure.
1571+
// Having a clone here allows us to embed a map iterator
1572+
// inside type MapIter so that MapIters can be re-used
1573+
// without doing any allocations.
1574+
type hiter struct {
1575+
key unsafe.Pointer
1576+
elem unsafe.Pointer
1577+
t unsafe.Pointer
1578+
h unsafe.Pointer
1579+
buckets unsafe.Pointer
1580+
bptr unsafe.Pointer
1581+
overflow *[]unsafe.Pointer
1582+
oldoverflow *[]unsafe.Pointer
1583+
startBucket uintptr
1584+
offset uint8
1585+
wrapped bool
1586+
B uint8
1587+
i uint8
1588+
bucket uintptr
1589+
checkBucket uintptr
1590+
}
1591+
1592+
func (h hiter) initialized() bool {
1593+
return h.t != nil
1594+
}
1595+
15691596
// A MapIter is an iterator for ranging over a map.
15701597
// See Value.MapRange.
15711598
type MapIter struct {
1572-
m Value
1573-
it unsafe.Pointer
1599+
m Value
1600+
hiter hiter
15741601
}
15751602

15761603
// Key returns the key of the iterator's current map entry.
15771604
func (it *MapIter) Key() Value {
1578-
if it.it == nil {
1605+
if !it.hiter.initialized() {
15791606
panic("MapIter.Key called before Next")
15801607
}
1581-
iterkey := mapiterkey(it.it)
1608+
iterkey := mapiterkey(&it.hiter)
15821609
if iterkey == nil {
15831610
panic("MapIter.Key called on exhausted iterator")
15841611
}
@@ -1592,10 +1619,10 @@ func (it *MapIter) Key() Value {
15921619
// It is equivalent to dst.Set(it.Key()), but it avoids allocating a new Value.
15931620
// As in Go, the key must be assignable to dst's type.
15941621
func (it *MapIter) SetKey(dst Value) {
1595-
if it.it == nil {
1622+
if !it.hiter.initialized() {
15961623
panic("MapIter.SetKey called before Next")
15971624
}
1598-
iterkey := mapiterkey(it.it)
1625+
iterkey := mapiterkey(&it.hiter)
15991626
if iterkey == nil {
16001627
panic("MapIter.SetKey called on exhausted iterator")
16011628
}
@@ -1616,10 +1643,10 @@ func (it *MapIter) SetKey(dst Value) {
16161643

16171644
// Value returns the value of the iterator's current map entry.
16181645
func (it *MapIter) Value() Value {
1619-
if it.it == nil {
1646+
if !it.hiter.initialized() {
16201647
panic("MapIter.Value called before Next")
16211648
}
1622-
iterelem := mapiterelem(it.it)
1649+
iterelem := mapiterelem(&it.hiter)
16231650
if iterelem == nil {
16241651
panic("MapIter.Value called on exhausted iterator")
16251652
}
@@ -1633,10 +1660,10 @@ func (it *MapIter) Value() Value {
16331660
// It is equivalent to dst.Set(it.Value()), but it avoids allocating a new Value.
16341661
// As in Go, the value must be assignable to dst's type.
16351662
func (it *MapIter) SetValue(dst Value) {
1636-
if it.it == nil {
1663+
if !it.hiter.initialized() {
16371664
panic("MapIter.SetValue called before Next")
16381665
}
1639-
iterelem := mapiterelem(it.it)
1666+
iterelem := mapiterelem(&it.hiter)
16401667
if iterelem == nil {
16411668
panic("MapIter.SetValue called on exhausted iterator")
16421669
}
@@ -1659,15 +1686,15 @@ func (it *MapIter) SetValue(dst Value) {
16591686
// entry. It returns false when the iterator is exhausted; subsequent
16601687
// calls to Key, Value, or Next will panic.
16611688
func (it *MapIter) Next() bool {
1662-
if it.it == nil {
1663-
it.it = mapiterinit(it.m.typ, it.m.pointer())
1689+
if !it.hiter.initialized() {
1690+
mapiterinit(it.m.typ, it.m.pointer(), &it.hiter)
16641691
} else {
1665-
if mapiterkey(it.it) == nil {
1692+
if mapiterkey(&it.hiter) == nil {
16661693
panic("MapIter.Next called on exhausted iterator")
16671694
}
1668-
mapiternext(it.it)
1695+
mapiternext(&it.hiter)
16691696
}
1670-
return mapiterkey(it.it) != nil
1697+
return mapiterkey(&it.hiter) != nil
16711698
}
16721699

16731700
// MapRange returns a range iterator for a map.
@@ -3216,19 +3243,17 @@ func mapassign(t *rtype, m unsafe.Pointer, key, val unsafe.Pointer)
32163243
//go:noescape
32173244
func mapdelete(t *rtype, m unsafe.Pointer, key unsafe.Pointer)
32183245

3219-
// m escapes into the return value, but the caller of mapiterinit
3220-
// doesn't let the return value escape.
32213246
//go:noescape
3222-
func mapiterinit(t *rtype, m unsafe.Pointer) unsafe.Pointer
3247+
func mapiterinit(t *rtype, m unsafe.Pointer, it *hiter)
32233248

32243249
//go:noescape
3225-
func mapiterkey(it unsafe.Pointer) (key unsafe.Pointer)
3250+
func mapiterkey(it *hiter) (key unsafe.Pointer)
32263251

32273252
//go:noescape
3228-
func mapiterelem(it unsafe.Pointer) (elem unsafe.Pointer)
3253+
func mapiterelem(it *hiter) (elem unsafe.Pointer)
32293254

32303255
//go:noescape
3231-
func mapiternext(it unsafe.Pointer)
3256+
func mapiternext(it *hiter)
32323257

32333258
//go:noescape
32343259
func maplen(m unsafe.Pointer) int

src/runtime/map.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ type bmap struct {
160160
}
161161

162162
// A hash iteration structure.
163-
// If you modify hiter, also change cmd/compile/internal/reflectdata/reflect.go to indicate
164-
// the layout of this structure.
163+
// If you modify hiter, also change cmd/compile/internal/reflectdata/reflect.go
164+
// and reflect/value.go to match the layout of this structure.
165165
type hiter struct {
166166
key unsafe.Pointer // Must be in first position. Write nil to indicate iteration end (see cmd/compile/internal/walk/range.go).
167167
elem unsafe.Pointer // Must be in second position (see cmd/compile/internal/walk/range.go).
@@ -806,14 +806,14 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
806806
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapiterinit))
807807
}
808808

809+
it.t = t
809810
if h == nil || h.count == 0 {
810811
return
811812
}
812813

813814
if unsafe.Sizeof(hiter{})/goarch.PtrSize != 12 {
814815
throw("hash_iter size incorrect") // see cmd/compile/internal/reflectdata/reflect.go
815816
}
816-
it.t = t
817817
it.h = h
818818

819819
// grab snapshot of bucket state
@@ -1336,10 +1336,8 @@ func reflect_mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
13361336
}
13371337

13381338
//go:linkname reflect_mapiterinit reflect.mapiterinit
1339-
func reflect_mapiterinit(t *maptype, h *hmap) *hiter {
1340-
it := new(hiter)
1339+
func reflect_mapiterinit(t *maptype, h *hmap, it *hiter) {
13411340
mapiterinit(t, h, it)
1342-
return it
13431341
}
13441342

13451343
//go:linkname reflect_mapiternext reflect.mapiternext

0 commit comments

Comments
 (0)