I don't get it.
Okay, this is how I understand the conservative GC to work:
- do allocations
- allocations increment
- at some time, we cross
- then we do a fullcollect, ie. mark, sweep
- then set the new threshold to
usedSmallPages * heapSizeFactor, which defaults to 2
- plus some smooth-decay magic that means threshold doesn't go down super fast
- unlock and resume
Rinse and repeat.
We have a service. It's pretty
std.json intensive, it handles a lot of networking on startup, and it has about 30 threads. When we start it up with everything at default settings, it uses 3.3GB.
This would indicate, given
heapSizeFactor=2 by default, that the high-water mark of used pages is 1.6GB.
We've tried setting
heapSizeFactor to 0.25. This is not quite equivalent to running the GC on every allocation (
smallCollectThreshold < usedSmallPages), but AIUI it first runs the GC every time it would allocate a new pool. It's pretty aggressive; something like 70% of our startup performance goes to GC. But, after startup, the service sits at RSS 961MB.
Here's the confusing part. RSS 961MB is an upper limit on the live memory. If this is correct, the
smallCollectThreshold after startup should be at most 1.6GB? Right? So the untuned GC should not let the process grow above 1.6GB? Right? But it's 3.3GB, more than twice that.
I mean, wrong, because we might get unlucky and run our GC when we have a lot of temporary memory live. But we'd need to have twice as much temporary memory truly live during startup as we do after startup, right? And if I watch the RSS during startup with
heapSizeFactor=0.25, I don't see it above 961MB ever. And it can't be that net queues run emptier during startup with
heapSizeFactor=0.25 than without, because the process is a lot slower with
heapSizeFactor=0.25 than without! It should clear queues faster with it off!
So what is the GC doing?!