-
Notifications
You must be signed in to change notification settings - Fork 18k
runtime: RSS creeps over 1GB even though heap is 4MB #13552
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
Comments
The given program in above post works fine on:
However it does become a memory hog on:
If I would want to investigate the root cause, where should I start from? |
I think the key is figuring out where all of those idle spans are coming from. It appears we're allocating new spans when we should be reusing existing spans, since the live heap never exceeds 5MB (just checked the gctrace), but we have 100's of megabytes worth of spans. Another piece of this puzzle might be figuring out why the scavenger is releasing almost none of these spans back to the OS. This might mean that we're systematically cycling through all of the spans so no individual span has been idle for long enough to be scavenged. |
b8b65c1.txt is the gctrace of this program running for about 1 hour and 45 minutes. Its RSS reached ~750MB. The live heap (the second heap size in the gctrace line) never exceeded 5 MB (checked with |
On reading this code: https://github.com/golang/go/blob/master/src/runtime/mheap.go#L872 It could be that actually stack is using more memory than heap. In the test program ( repeated below ) we are only allocating channels:
My analysis could be totally wrong. |
I believe you're right that the channel and stacks are the only allocations. It turns out that However, there's also not much stack in use. I ran it with Go 1.5.1, let the heap reach 3.8GB (which took a matter of seconds) and here's runtime.memstats:
Some of these fields are out of date because they're cached in various places, but heap_sys and heap_idle are many gigabytes, while stacks_inuse (which is updated eagerly) is only 23MB. It may be that these spans were allocated for stack use, but we're not holding on to them for stack use. |
I suspect the rapid memory explosion in Go 1.5.1 is a consequence of #11466. Because I suspect this is better on tip simply because GC scheduling is a lot tighter, so the window for falling behind is much smaller. However, this doesn't explain why there's a slow RSS growth on tip over the course of thousands of GCs, since each GC cycle should clean up all of these stacks and make their spans available for reuse and the allocations following that should simply reuse them. |
I've confirmed my hypothesis about Go 1.5.1. I set ulimit -v to 1GB and let it crash and the npages across all of the to-be-freed stacks in stackFreeQueue totals to 967MB. So #11466 explains the rapid memory consumption in Go 1.5.1. But it doesn't yet explain the slow growth on tip. /cc @RLH. This is an interesting case where delaying the freeing of stacks is really an issue. |
@aclements Thanks for your pointers, very helpful for me to understand what is going on underneath. I was wondering where we set the frequency of GC cycles, either explicitly via tooling / APIs, or implicitly in Go compiler's code. |
@tuxdna, the scheduling of GC cycles and GC work is done dynamically by a control system in the runtime: https://golang.org/s/go15gcpacing. For much of the implementation, see gcControllerState and where the runtime sets memstats.next_gc. |
It turns out there's simply a long, low-probability tail on how much memory is tied up in unfreed large stack spans. That means if you run the program long enough, you'll eventually have a single GC cycle that ties up over a GB in stack spans (if you were sufficiently unlucky, I don't think there's anything stopping this from happening early in an execution). I added a debug print to report how much memory in large stack spans is being freed during mark termination and here's what the distribution looked like after just two minutes:
|
Couldn't we allow stackalloc to allocate from stackFreeQueue? If we kept On Mon, Dec 14, 2015 at 11:41 AM, Austin Clements [email protected]
|
I've done exactly that, actually. I'm trying (and failing) to write a test and I'll send out a CL. |
Great minds think alike. On Mon, Dec 14, 2015 at 12:32 PM, Austin Clements [email protected]
|
CL https://golang.org/cl/17814 mentions this issue. |
This bug was originally reported by Ilia Kandrashou in https://groups.google.com/d/msg/golang-nuts/AVstzvKAej4/v1F_wreDCAAJ.
The test program at http://play.golang.org/p/zMHtw0-8Ph causes the process' RSS to slowly creep up over time. On a test amd64 machine running Linux 4.1.13-1-lts with GOMAXPROCS=16 at current master (b8b65c1), the RSS reaches 1GB after about 2 hours. According to gctrace=1, the live heap remains under 4MB the whole time (which it should):
The scavenger trace indicates that we're losing all of this memory in idle spans:
In Go 1.5.1, the RSS surpassed 1GB in less than 30 seconds, while the gctrace never reported a live heap over 22MB, so we've at least improved things. I haven't tested Go 1.5.2 yet, but according to the original report, the memory grows slowly but surely.
The text was updated successfully, but these errors were encountered: