aboutsummaryrefslogtreecommitdiff
path: root/src/reflect
diff options
context:
space:
mode:
authorJosh Bleecher Snyder <josharian@gmail.com>2021-05-20 09:57:04 -0700
committerJosh Bleecher Snyder <josharian@gmail.com>2021-09-05 23:09:32 +0000
commit1b2d794ca3ba60c2dbc958a271662784a7122739 (patch)
treeb0bcc00ae0940735212a0a33e30cd6c85871ca55 /src/reflect
parent9133245be7365c23fcd60e3bb60ebb614970cdab (diff)
downloadgo-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.go7
-rw-r--r--src/reflect/value.go73
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