-
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
runtime: shrink map as elements are deleted #20135
Comments
/cc @randall77, @josharian |
I'm surprised there isn't a dup of this already in the issue tracker. Yes, maps that shrink permanently currently never get cleaned up after. As usual, the implementation challenge is with iterators. Maps that shrink and grow repeatedly used to also cause leaks. That was #16070, fixed by CL 25049. I remember hoping when I started on that CL that the same mechanism would be useful for shrinking maps as well, but deciding it wouldn't. Sadly, I no longer remember why. If anyone wants to investigate this issue, I'd start by looking at that CL and thinking about whether that approach could be extended to shrinking maps. The only available workaround is to make a new map and copy in elements from the old. |
just an observation - adding |
Any update on this issue? we load 1 Million entry into map. No matter we try to delete the value or set the map nil, seems the memory is always increasing until OOM. |
@hixichen: see @josharian's workaround above:
That is, you have to let the entire map be garbage-collected. Then all its memory will eventually be made available again, and you can start using a new and smaller map. If this doesn't work, please provide a small Go program to reproduce the problem. As for progress - if there was any, you'd see it in this thread. |
Is this really an efficient way to handle this issue? |
@hixichen what happens if you set the map to This may help differentiate between a "GC-issue" and a "returning memory to the OS" issue. Edit: It seems you're using Go itself to gauge memory allocation so this message can be ignored (perhaps it will be useful to someone else so I'll post it anyway). |
You can do it efficiently by delaying shrinking until you've done O(n) deletes. |
@as yes, I am using Go itself to gauge memory allocation, and, personally I think Go should handle it by itself. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I'd expect GO to handle memory both ways here. This is unintuitive behavior and should be noted in the map docs until resolved. I just realized we have multiple eventual OOM's in our system. |
go version go1.13.1 darwin/amd64 I have a question: package main
import (
"fmt"
"runtime"
)
func main() {
v := struct{}{}
a := make(map[int]struct{})
for i := 0; i < 10000; i++ {
a[i] = v
}
runtime.GC()
printMemStats("After Map Add 100000")
for i := 0; i < 10000-1; i++ {
delete(a, i)
}
runtime.GC()
printMemStats("After Map Delete 9999")
for i := 0; i < 10000-1; i++ {
a[i] = v
}
runtime.GC()
printMemStats("After Map Add 9999 again")
a = nil
runtime.GC()
printMemStats("After Map Set nil")
}
func printMemStats(mag string) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("%v:memory = %vKB, GC Times = %v\n", mag, m.Alloc/1024, m.NumGC)
} output:
Why a local var map |
The map will be garbage collected at this Try adding |
This comment has been minimized.
This comment has been minimized.
Isn't the issue is that the GC is not triggered at the right point? GC process is able to recognize that the map has some deleted keys, that's why Also, I believe it is a pretty serious issue. Allocating a new map should be documented as best practices when using go. |
It looks like it no longer the case? I'm deleting half of the elements and the map gets shrinked. I'm also preserving the last map by accessing the last element. |
If you delete all elements from the map, or you preallocate the map, you'll see that that's not the case. That decrease in memory that demo shows is likely to be just a byproduct artifact of how incremental map growth works. |
@DmitriyMV, you should call |
The result for your new demo program is indeed some weird.
The shrink only happens when mapSize is in [98_305, 109_635], but not out of the range. [edit]: It looks there are some randomness here. The right boundary changes to 109_664 in my new wave of tests. |
@go101 shrink doesn't happen if you preallocate all |
Yes, what you said is true. But the confusion is not cleared. Which part of memory is freed when half of elements are deleted? [edit]: more precisely, at the point of "after map creation", it looks to me that some memory should be collected but it is not. |
why not use community map :https://github.com/dolthub/swiss |
@introspection3 see #54766. |
Go doesn't free map blocks, even after a map shrinks considerably. The dedupe buffer tends to store a lot of keys for a start-of-day snapshot, make sure we clean up the leaked map capacity once we're back down to zero. Upstream issue: golang/go#20135
Go doesn't free map blocks, even after a map shrinks considerably. The dedupe buffer tends to store a lot of keys for a start-of-day snapshot, make sure we clean up the leaked map capacity once we're back down to zero. Upstream issue: golang/go#20135
Go doesn't free map blocks, even after a map shrinks considerably. The dedupe buffer tends to store a lot of keys for a start-of-day snapshot, make sure we clean up the leaked map capacity once we're back down to zero. Upstream issue: golang/go#20135
Go doesn't free map blocks, even after a map shrinks considerably. The dedupe buffer tends to store a lot of keys for a start-of-day snapshot, make sure we clean up the leaked map capacity once we're back down to zero. Upstream issue: golang/go#20135
Go doesn't free map blocks, even after a map shrinks considerably. The dedupe buffer tends to store a lot of keys for a start-of-day snapshot, make sure we clean up the leaked map capacity once we're back down to zero. Upstream issue: golang/go#20135
What version of Go are you using (
go version
)?go version go1.8 windows/amd64
What operating system and processor architecture are you using (
go env
)?set GOARCH=amd64
set GOBIN=
set GOEXE=.exe
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOOS=windows
set GOPATH=C:\dev\Go
set GORACE=
set GOROOT=C:\Go
set GOTOOLDIR=C:\Go\pkg\tool\windows_amd64
set GCCGO=gccgo
set CC=gcc
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0
set CXX=g++
set CGO_ENABLED=1
set PKG_CONFIG=pkg-config
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
What did you do?
See example on playground: https://play.golang.org/p/odsk9F1UH1
(edit: forgot to remove sleeps and changed the number of elements)
What did you expect to see?
removing elements from m1 map should release memory.
What did you see instead?
total allocated memory is always increasing
In the example the issue is not so relevant, but in my production scenario (several maps with more than 1million elements each) I can easily get OOM error, and the process is being killed.
Also I don't know if memstats.Alloc is the right counter to expose here, but I can observe the issue with regular process management tools in linux (e.g. top or htop)
The text was updated successfully, but these errors were encountered: