-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
cmd/compile: recognize map-clearing range idiom #20138
Comments
Another way to do that is: m = map[K]V{} How common is this? I think more common is: for k, v := range m { // m == work to do
doSomeWork(k, v)
delete(m, k)
} ... but that probably wouldn't match. |
This is different. In the range code the map is emptied, in this, it is replaced with a new, empty map. Consider x := m
// range clear or assign new empty map
x[0] = "a"
println(m[0]) These will yield different results based on the range clear vs assignment. And in practice, they have very different performance characteristics, which is why the range loop gets used instead of just abandoning the old map.
Flipping through the cmd subdir, I see:
If the range delete idiom were fast, I suspect that this would get rewritten into two loops in performance-critical code, one to do the work and one to empty the map. |
I had a use case for this too and would like to work on it as discussed with josharian. The route of clearing all existing buckets and removing extra overflow buckets sounds good. just supporting: for k := range m {
delete(m, k)
} for now is consistent with slice clearing and provides an easy idiom to fast clearing of maps and reusing the memory. for k, v := range m { // m == work to do
doSomeWork(k, v)
delete(m, k)
} can just be written as for k, v := range m {
doSomeWork(k, v)
}
for k, v := range m {
delete(m, k)
} if performance is critical. For anything additional in the loop it is currently to complex to detemine in the frontend that nothing else changes the map. i think the side effects of the code above are less surprising than if the result (map backing memory) m = map[K]V{} And it seems inconsistent with: s := make([]int, 0, 10)
s = []int{} not resulting in cap(s) == 10 currently. |
Two tricky cases:
We need to avoid the optimization here because
If there are |
To add to @randall77's mentioned tricky cases in #20138 (comment), it might seem a little obvious, but there is another one in which doWork is not a pure function in regards to m, or actually does something with m doWork := func(k, v) {
if m[k+1] % 2 == 0 {
return
}
// do the work or even modifies m
}
for k, v := range m {
doWork(k, v)
delete(m, k)
} in this case we just can't convert it to for k, v := range m {
doWork(k, v)
}
for k := range m {
delete(m, k)
}
// or
runtime_mapclear(m) |
@odeke-em indeed. As a start it suffices to recognize the simple case: for k, v := range m {
delete(m, k)
} as this is consistent with the detection capability of the slice/array clearing idiom. Later we can think about making all the clearing idioms more sophisticated if warranted. |
Spitballing: would it be possible for the compiler to recognise this
m = make(map[k]v)
as the clearing idiom.
That is, if m is already defined in the current scope, and not nil, then
clear it, rather than create a new map and cast the other aside.
…On Mon, Jul 31, 2017 at 4:39 AM, Martin Möhrmann ***@***.***> wrote:
@odeke-em <https://github.com/odeke-em> indeed. As a start it suffices to
recognize the simple case:
for k, v := range m {
delete(m, k)
}
as this is consistent with the detection capability of the slice/array
clearing idiom.
Later we can think about making all the clearing idioms more sophisticated
if warranted.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#20138 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAAcA9KguNZiGlzZYaAIjTw3kXuzLIi6ks5sTM3XgaJpZM4NJOBh>
.
|
@davecheney, that would require it can prove that no references to the old Whereas just recognizing the |
If m is already defined in then assigning a new map, via make, and deleting
all the keys in the current map has the same effect, right?
…On Mon, Jul 31, 2017 at 9:21 AM, Brad Fitzpatrick ***@***.***> wrote:
@davecheney <https://github.com/davecheney>, that would require it can
prove that no references to the old m map value exist in a live state
anywhere. I wouldn't hold my breath waiting for such things. (e.g. #2205
<#2205>)
Whereas just recognizing the for-delete loop doesn't require any new
analysis.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#20138 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAAcA2xNEYj62LuPVWS_ARU1b4ed4_4Cks5sTRAQgaJpZM4NJOBh>
.
|
You can't clear |
Could something similar be done for map copies? That is, recognising:
A quick search yields a dozen matches in the tree (ignore the one with
For the sake of completeness, here's the command one can use to find instances of the map-clearing range idiom:
|
Still coding up the solution to this one for 1.11. Map copies could be done but it would need to be considered how this will interact when the copied map is changed during the copy so the new copied map does not end up corrupted. Also needs to honor the "If a map entry that has not yet been reached is removed during iteration, the corresponding iteration value will not be produced." semantics. Best to open a new issue to discuss the details. Instead of doing all this pattern matching which also is made harder by usually being applied after the order pass we might want to consider having a generic clear and shallow copy function working on maps (or all types) for go 2. Those functions could also handle e.g. the NaN key case differently (clear the entry) to allow for a simpler and faster implementation. |
Change https://golang.org/cl/110055 mentions this issue: |
This complements the dynamic checks, which is already implemented in setArgs and in UnpackeArgs. Updates bazelbuild/starlark#21 Change-Id: If18c9b1f228149b67eca9637050e76e94bd9ff85
Sometimes, maps get emptied for re-use. Idiom:
This is pretty inefficient, though; it involves juggling iterators and making one delete call per map element. This could be recognized by cmd/compile and converted into a (newly added) runtime mapclear call, which could just walk and clear the buckets directly.
We could also decide to have it free all extra overflow buckets that have been allocated for it, which would ameliorate some of the problem in #20135. (The main bucket array and pre-allocated overflow buckets would remain, but that's desirable; emptying a map and re-filling is an intentional way to reuse those.) This would make the implementation of mapclear very efficient, mostly just a typedmemclr of hmap.buckets.
cc @randall77 @martisch
The text was updated successfully, but these errors were encountered: