Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reflect: add MapIter.Reset #46293

Closed
josharian opened this issue May 20, 2021 · 11 comments
Closed

reflect: add MapIter.Reset #46293

josharian opened this issue May 20, 2021 · 11 comments

Comments

@josharian
Copy link
Contributor

josharian commented May 20, 2021

Currently, iterating over a map requires two allocations: A reflect.MapIter and a runtime.hiter. By embedding a runtime.hiter (or rather, a type with the same shape and size) in a reflect.MapIter, it is possible to eliminate the second allocation. If we could re-use a reflect.MapIter to iterate over multiple maps, then we could eliminate the first allocation as well.

I propose that we add:

// Reset changes iter to iterate over v.
// It panics if v's Kind is not Map and v is not the zero Value.
func (iter *MapIter) Reset(v Value)

Allowing v to be the zero Value enables the caller to avoid pinning whatever map iter was previously iterating over.

Note that these two would be equivalent:

iter := new(reflect.MapIter)
iter.Reset(mapVal)
iter := mapVal.MapRange()

cc @bradfitz @randall77

@dsnet

This comment has been minimized.

@josharian

This comment has been minimized.

josharian added a commit to tailscale/go that referenced this issue May 21, 2021
This reduces the number of allocations per
reflect map iteration from two to one.

For golang#46293

Change-Id: Ibcff5f42fc512e637b6e460bad4518e7ac83d4c3
josharian added a commit to tailscale/go that referenced this issue May 21, 2021
it := new(reflect.MapIter)
it.Next()

This generates a nil pointer dereference panic from reflect.Value.pointer.
Generate a clearer panic.

For golang#46293

Change-Id: I32a22c797e1ba3a7b4e70b38ceb4dedb44d264fa
josharian added a commit to tailscale/go that referenced this issue May 21, 2021
This allows callers to do (amortized) allocation-free iteration
over many maps.

Fixes golang#46293

Change-Id: I3aa6134dd00da35b508bd1e3b487332a871a3673
@gopherbot
Copy link
Contributor

Change https://golang.org/cl/321891 mentions this issue: reflect: add MapIter.Reset

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/321890 mentions this issue: reflect: improve panic when MapIter has no associated map Value

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/321889 mentions this issue: reflect: allocate hiter as part of MapIter

josharian added a commit to tailscale/tailscale that referenced this issue May 21, 2021
name              old time/op    new time/op    delta
Hash-8              12.4µs ± 0%    12.4µs ± 0%    -0.33%  (p=0.002 n=10+9)
HashMapAcyclic-8    21.2µs ± 0%    21.3µs ± 0%    +0.45%  (p=0.000 n=8+8)

name              old alloc/op   new alloc/op   delta
Hash-8                793B ± 0%      408B ± 0%   -48.55%  (p=0.000 n=10+10)
HashMapAcyclic-8      128B ± 0%        0B       -100.00%  (p=0.000 n=10+10)

name              old allocs/op  new allocs/op  delta
Hash-8                9.00 ± 0%      6.00 ± 0%   -33.33%  (p=0.000 n=10+10)
HashMapAcyclic-8      1.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)

Depends on golang/go#46293.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
josharian added a commit to tailscale/go that referenced this issue May 24, 2021
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
josharian added a commit to tailscale/go that referenced this issue May 24, 2021
… map Value

it := new(reflect.MapIter)
it.Next()

This generates a nil pointer dereference panic from reflect.Value.pointer.
Generate a clearer panic.

For golang#46293

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

Change-Id: I32a22c797e1ba3a7b4e70b38ceb4dedb44d264fa
josharian added a commit to tailscale/go that referenced this issue May 24, 2021
This allows callers to do (amortized) allocation-free iteration
over many maps.

Fixes golang#46293

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

Change-Id: I3aa6134dd00da35b508bd1e3b487332a871a3673
josharian added a commit to tailscale/tailscale that referenced this issue May 24, 2021
name              old time/op    new time/op    delta
Hash-8              12.4µs ± 0%    12.4µs ± 0%    -0.33%  (p=0.002 n=10+9)
HashMapAcyclic-8    21.2µs ± 0%    21.3µs ± 0%    +0.45%  (p=0.000 n=8+8)

name              old alloc/op   new alloc/op   delta
Hash-8                793B ± 0%      408B ± 0%   -48.55%  (p=0.000 n=10+10)
HashMapAcyclic-8      128B ± 0%        0B       -100.00%  (p=0.000 n=10+10)

name              old allocs/op  new allocs/op  delta
Hash-8                9.00 ± 0%      6.00 ± 0%   -33.33%  (p=0.000 n=10+10)
HashMapAcyclic-8      1.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)

Depends on golang/go#46293.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
josharian added a commit to tailscale/tailscale that referenced this issue May 24, 2021
name              old time/op    new time/op    delta
Hash-8              12.4µs ± 0%    12.4µs ± 0%    -0.33%  (p=0.002 n=10+9)
HashMapAcyclic-8    21.2µs ± 0%    21.3µs ± 0%    +0.45%  (p=0.000 n=8+8)

name              old alloc/op   new alloc/op   delta
Hash-8                793B ± 0%      408B ± 0%   -48.55%  (p=0.000 n=10+10)
HashMapAcyclic-8      128B ± 0%        0B       -100.00%  (p=0.000 n=10+10)

name              old allocs/op  new allocs/op  delta
Hash-8                9.00 ± 0%      6.00 ± 0%   -33.33%  (p=0.000 n=10+10)
HashMapAcyclic-8      1.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)

Depends on golang/go#46293.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
josharian added a commit to tailscale/tailscale that referenced this issue May 24, 2021
name              old time/op    new time/op    delta
Hash-8              12.4µs ± 0%    12.4µs ± 0%    -0.33%  (p=0.002 n=10+9)
HashMapAcyclic-8    21.2µs ± 0%    21.3µs ± 0%    +0.45%  (p=0.000 n=8+8)

name              old alloc/op   new alloc/op   delta
Hash-8                793B ± 0%      408B ± 0%   -48.55%  (p=0.000 n=10+10)
HashMapAcyclic-8      128B ± 0%        0B       -100.00%  (p=0.000 n=10+10)

name              old allocs/op  new allocs/op  delta
Hash-8                9.00 ± 0%      6.00 ± 0%   -33.33%  (p=0.000 n=10+10)
HashMapAcyclic-8      1.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)

Depends on golang/go#46293.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
simenghe pushed a commit to tailscale/tailscale that referenced this issue May 27, 2021
name              old time/op    new time/op    delta
Hash-8              12.4µs ± 0%    12.4µs ± 0%    -0.33%  (p=0.002 n=10+9)
HashMapAcyclic-8    21.2µs ± 0%    21.3µs ± 0%    +0.45%  (p=0.000 n=8+8)

name              old alloc/op   new alloc/op   delta
Hash-8                793B ± 0%      408B ± 0%   -48.55%  (p=0.000 n=10+10)
HashMapAcyclic-8      128B ± 0%        0B       -100.00%  (p=0.000 n=10+10)

name              old allocs/op  new allocs/op  delta
Hash-8                9.00 ± 0%      6.00 ± 0%   -33.33%  (p=0.000 n=10+10)
HashMapAcyclic-8      1.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)

Depends on golang/go#46293.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
Signed-off-by: Simeng He <simeng@tailscale.com>
@rsc
Copy link
Contributor

rsc commented Jun 2, 2021

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@rsc
Copy link
Contributor

rsc commented Jun 9, 2021

Does anyone object to this proposal?

@randall77
Copy link
Contributor

Seems ok to me.

I think there is a small issue with calling Reset for a map of different type. It constrains somewhat how we represent an hiter inside the compiler. We don't do any such thing currently, but we could for example embed the key by value instead of by pointer. Then on a reset with a different type we'd need to reallocate the hiter which is what you're trying to avoid. But this is all hypothetical and even in the worst case means reflect map iteration would at most become slow/allocatey again.

@rsc
Copy link
Contributor

rsc commented Jun 16, 2021

Based on the discussion above, this proposal seems like a likely accept.
— rsc for the proposal review group

@rsc
Copy link
Contributor

rsc commented Jul 14, 2021

No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.
— rsc for the proposal review group

@rsc rsc changed the title proposal: reflect: add MapIter.Reset reflect: add MapIter.Reset Jul 14, 2021
@rsc rsc modified the milestones: Proposal, Backlog Jul 14, 2021
josharian added a commit to tailscale/go that referenced this issue Jul 29, 2021
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
josharian added a commit to tailscale/go that referenced this issue Jul 29, 2021
… map Value

it := new(reflect.MapIter)
it.Next()

This generates a nil pointer dereference panic from reflect.Value.pointer.
Generate a clearer panic.

For golang#46293

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

Change-Id: I32a22c797e1ba3a7b4e70b38ceb4dedb44d264fa
josharian added a commit to tailscale/go that referenced this issue Jul 29, 2021
This allows callers to do (amortized) allocation-free iteration
over many maps.

Fixes golang#46293

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

Change-Id: I3aa6134dd00da35b508bd1e3b487332a871a3673
josharian added a commit to tailscale/go that referenced this issue Jul 30, 2021
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
josharian added a commit to tailscale/go that referenced this issue Jul 30, 2021
… map Value

it := new(reflect.MapIter)
it.Next()

This generates a nil pointer dereference panic from reflect.Value.pointer.
Generate a clearer panic.

For golang#46293

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

Change-Id: I32a22c797e1ba3a7b4e70b38ceb4dedb44d264fa
josharian added a commit to tailscale/go that referenced this issue Jul 30, 2021
This allows callers to do (amortized) allocation-free iteration
over many maps.

Fixes golang#46293

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

Change-Id: I3aa6134dd00da35b508bd1e3b487332a871a3673
josharian added a commit to tailscale/go that referenced this issue Aug 5, 2021
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
josharian added a commit to tailscale/go that referenced this issue Aug 5, 2021
… map Value

it := new(reflect.MapIter)
it.Next()

This generates a nil pointer dereference panic from reflect.Value.pointer.
Generate a clearer panic.

For golang#46293

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

Change-Id: I32a22c797e1ba3a7b4e70b38ceb4dedb44d264fa
josharian added a commit to tailscale/go that referenced this issue Aug 5, 2021
This allows callers to do (amortized) allocation-free iteration
over many maps.

Fixes golang#46293

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

Change-Id: I3aa6134dd00da35b508bd1e3b487332a871a3673
gopherbot pushed a commit that referenced this issue Sep 5, 2021
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>
gopherbot pushed a commit that referenced this issue Sep 5, 2021
it := new(reflect.MapIter)
it.Next()

This generates a nil pointer dereference panic from reflect.Value.pointer.
Generate a clearer panic.

For #46293

Change-Id: I32a22c797e1ba3a7b4e70b38ceb4dedb44d264fa
Reviewed-on: https://go-review.googlesource.com/c/go/+/321890
Trust: Josh Bleecher Snyder <josharian@gmail.com>
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/400675 mentions this issue: reflect: make Value.MapRange inlineable

gopherbot pushed a commit that referenced this issue Apr 18, 2022
This allows the caller to decide whether MapIter should be
stack allocated or heap allocated based on whether it escapes.
In most cases, it does not escape and thus removes the utility
of MapIter.Reset (#46293). In fact, use of sync.Pool with MapIter
and calling MapIter.Reset is likely to be slower.

Change-Id: Ic93e7d39e5dd4c83e7fca9e0bdfbbcd70777f0e1
Reviewed-on: https://go-review.googlesource.com/c/go/+/400675
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
@golang golang locked and limited conversation to collaborators Apr 17, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants