diff options
author | Josh Bleecher Snyder <josharian@gmail.com> | 2021-05-20 09:57:04 -0700 |
---|---|---|
committer | Josh Bleecher Snyder <josharian@gmail.com> | 2021-09-05 23:09:32 +0000 |
commit | 1b2d794ca3ba60c2dbc958a271662784a7122739 (patch) | |
tree | b0bcc00ae0940735212a0a33e30cd6c85871ca55 /src/reflect | |
parent | 9133245be7365c23fcd60e3bb60ebb614970cdab (diff) | |
download | go-1b2d794ca3ba60c2dbc958a271662784a7122739.tar.gz go-1b2d794ca3ba60c2dbc958a271662784a7122739.zip |
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>
Diffstat (limited to 'src/reflect')
-rw-r--r-- | src/reflect/all_test.go | 7 | ||||
-rw-r--r-- | src/reflect/value.go | 73 |
2 files changed, 52 insertions, 28 deletions
diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 40ac6a95fa..6cb603cb16 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -370,10 +370,9 @@ func TestMapIterSet(t *testing.T) { iter.SetValue(e) } })) - // Making a *MapIter and making an hiter both allocate. - // Those should be the only two allocations. - if got != 2 { - t.Errorf("wanted 2 allocs, got %d", got) + // Making a *MapIter allocates. This should be the only allocation. + if got != 1 { + t.Errorf("wanted 1 alloc, got %d", got) } } diff --git a/src/reflect/value.go b/src/reflect/value.go index a8274cc871..1a61cb897c 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1549,11 +1549,12 @@ func (v Value) MapKeys() []Value { if m != nil { mlen = maplen(m) } - it := mapiterinit(v.typ, m) + var it hiter + mapiterinit(v.typ, m, &it) a := make([]Value, mlen) var i int for i = 0; i < len(a); i++ { - key := mapiterkey(it) + key := mapiterkey(&it) if key == nil { // Someone deleted an entry from the map since we // called maplen above. It's a data race, but nothing @@ -1561,24 +1562,50 @@ func (v Value) MapKeys() []Value { break } a[i] = copyVal(keyType, fl, key) - mapiternext(it) + mapiternext(&it) } 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. // See Value.MapRange. type MapIter struct { - m Value - it unsafe.Pointer + m Value + hiter hiter } // Key returns the key of the iterator's current map entry. func (it *MapIter) Key() Value { - if it.it == nil { + if !it.hiter.initialized() { panic("MapIter.Key called before Next") } - iterkey := mapiterkey(it.it) + iterkey := mapiterkey(&it.hiter) if iterkey == nil { 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. // As in Go, the key must be assignable to dst's type. func (it *MapIter) SetKey(dst Value) { - if it.it == nil { + if !it.hiter.initialized() { panic("MapIter.SetKey called before Next") } - iterkey := mapiterkey(it.it) + iterkey := mapiterkey(&it.hiter) if iterkey == nil { 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. func (it *MapIter) Value() Value { - if it.it == nil { + if !it.hiter.initialized() { panic("MapIter.Value called before Next") } - iterelem := mapiterelem(it.it) + iterelem := mapiterelem(&it.hiter) if iterelem == nil { 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. // As in Go, the value must be assignable to dst's type. func (it *MapIter) SetValue(dst Value) { - if it.it == nil { + if !it.hiter.initialized() { panic("MapIter.SetValue called before Next") } - iterelem := mapiterelem(it.it) + iterelem := mapiterelem(&it.hiter) if iterelem == nil { 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 // calls to Key, Value, or Next will panic. func (it *MapIter) Next() bool { - if it.it == nil { - it.it = mapiterinit(it.m.typ, it.m.pointer()) + if !it.hiter.initialized() { + mapiterinit(it.m.typ, it.m.pointer(), &it.hiter) } else { - if mapiterkey(it.it) == nil { + if mapiterkey(&it.hiter) == nil { 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. @@ -3216,19 +3243,17 @@ func mapassign(t *rtype, m unsafe.Pointer, key, val unsafe.Pointer) //go:noescape 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 -func mapiterinit(t *rtype, m unsafe.Pointer) unsafe.Pointer +func mapiterinit(t *rtype, m unsafe.Pointer, it *hiter) //go:noescape -func mapiterkey(it unsafe.Pointer) (key unsafe.Pointer) +func mapiterkey(it *hiter) (key unsafe.Pointer) //go:noescape -func mapiterelem(it unsafe.Pointer) (elem unsafe.Pointer) +func mapiterelem(it *hiter) (elem unsafe.Pointer) //go:noescape -func mapiternext(it unsafe.Pointer) +func mapiternext(it *hiter) //go:noescape func maplen(m unsafe.Pointer) int |