Skip to content

Commit

Permalink
[tailscale1.16] reflect: allocate hiter as part of MapIter
Browse files Browse the repository at this point in the history
This reduces the number of allocations per
reflect map iteration from two to one.

For golang#46293

(cherry picked from golang.org/cl/321889)

Change-Id: Ibcff5f42fc512e637b6e460bad4518e7ac83d4c3
  • Loading branch information
josharian committed May 24, 2021
1 parent 728ecc5 commit 5740ae9
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 16 deletions.
7 changes: 3 additions & 4 deletions src/reflect/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,10 +367,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)
}
}

Expand Down
37 changes: 30 additions & 7 deletions src/reflect/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -1212,7 +1212,8 @@ func (v Value) MapKeys() []Value {
if m != nil {
mlen = maplen(m)
}
it := mapiterinit(v.typ, m)
it := unsafe.Pointer(new(hiter))
mapiterinit(v.typ, m, it)
a := make([]Value, mlen)
var i int
for i = 0; i < len(a); i++ {
Expand All @@ -1229,11 +1230,34 @@ func (v Value) MapKeys() []Value {
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
}

// A MapIter is an iterator for ranging over a map.
// See Value.MapRange.
type MapIter struct {
m Value
it unsafe.Pointer
m Value
it unsafe.Pointer // nil or points to hiter field
hiter hiter
}

// Key returns the key of the iterator's current map entry.
Expand Down Expand Up @@ -1319,7 +1343,8 @@ func (it *MapIter) SetValue(dst Value) {
// 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())
it.it = unsafe.Pointer(&it.hiter)
mapiterinit(it.m.typ, it.m.pointer(), it.it)
} else {
if mapiterkey(it.it) == nil {
panic("MapIter.Next called on exhausted iterator")
Expand Down Expand Up @@ -2835,10 +2860,8 @@ 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 unsafe.Pointer)

//go:noescape
func mapiterkey(it unsafe.Pointer) (key unsafe.Pointer)
Expand Down
8 changes: 3 additions & 5 deletions src/runtime/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ type bmap struct {
}

// A hash iteration structure.
// If you modify hiter, also change cmd/compile/internal/gc/reflect.go to indicate
// the layout of this structure.
// If you modify hiter, also change cmd/compile/internal/reflectdata/reflect.go
// and reflect/value.go to match the layout of this structure.
type hiter struct {
key unsafe.Pointer // Must be in first position. Write nil to indicate iteration end (see cmd/compile/internal/gc/range.go).
elem unsafe.Pointer // Must be in second position (see cmd/compile/internal/gc/range.go).
Expand Down Expand Up @@ -1335,10 +1335,8 @@ func reflect_mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
}

//go:linkname reflect_mapiterinit reflect.mapiterinit
func reflect_mapiterinit(t *maptype, h *hmap) *hiter {
it := new(hiter)
func reflect_mapiterinit(t *maptype, h *hmap, it *hiter) {
mapiterinit(t, h, it)
return it
}

//go:linkname reflect_mapiternext reflect.mapiternext
Expand Down

0 comments on commit 5740ae9

Please sign in to comment.