You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GC: implement a grow-vs-collect heuristic. (#12942)
* GC: implement a grow-vs-collect heuristic.
This implements the heuristic discussed in #12860: it replaces the
existing behavior where Wasmtime's GC, when allocating, will continue
growing the GC heap up to its size limit before initiating a
collection. That behavior optimizes for allocation performance but at
the cost of resident memory size -- it is at one extreme end of that
tradeoff spectrum.
There are a number of use-cases where there may be heavy allocation
traffic but a relatively small live-heap size compared to the total
volume of allocations. For example, lots of temporary "garbage" may be
allocated by many workloads. Or, more pertinently to #12860, a C/C++
workload that uses the underlying GC heap only for exceptions, and
uses those exceptions in a way that only one `exnref` is live at a
time (no `exn` objects are stashed away and used later), will also
generate a lot of "garbage" during normal execution. These kinds of
workloads benefit significantly from more frequent collection to keep
the resident-set size small. This also may benefit performance, even
accounting for the cost of the collection itself, because it keeps
the footprint of touched memory within higher cache-hierarchy levels.
In order to accommodate that kind of workload while also presenting
reasonable behavior to large-working-set-size benchmarks, it is
desirable to implement an *adaptive* policy. To that end, this PR
implements a scheme similar to our OwnedRooted allocation/collection
algorithm (and specified explicitly [here] by fitzgen): we use the
last live-heap size (post-collection) compared to current capacity to
decide whether to grow or collect. When the current capacity is more
than twice the last live-heap size, we collect first; if we still
can't allocate, then we grow. Otherwise, we just grow.
The idea is that (when combined with an exponential heap-growth rule)
the continuous-allocation case will collect once at each power-of-two,
then grow; this is "amortized constant time" overhead. A case with a
stable working-set size but with some ups and downs will never hit a
"threshold-thrashing" problem: the heap capacity will tend toward
twice the live-heap size, in the steady state (see proof [here](proof)
for the analogous algorithm for `OwnedRooted`). Thus we have a nice,
deterministic bound no matter what, with no bad (quadratic or worse)
cases.
This PR adds a test that creates a bunch of almost-immediately-dead
garbage (allocates a GC struct that is only live for one iteration of
a loop) and checks the heap size at each iteration. To allow this
check, it also adds a method to `Store` to get the current GC heap
capacity, which seems like a generally useful kind of observability as
well.
[here]: #12860 (comment)
[proof]: https://github.com/bytecodealliance/wasmtime/blob/e5b127ccd71dbd7d447a32722b2c699abc46fe61/crates/wasmtime/src/runtime/gc/enabled/rooting.rs#L617-L678
* Review feedback.
* Fix null-GC test by making `gorw_gc_heap` libcall always grow.
* Fix indentation.
* Fix merge from main.
* fix merge from main
* Fix OOM test after merge from main.
0 commit comments