diff options
author | Ben Gamari <ben@smart-cactus.org> | 2019-05-08 21:28:35 -0400 |
---|---|---|
committer | Ben Gamari <ben@smart-cactus.org> | 2019-10-22 12:18:39 -0400 |
commit | 8e79e2a973c1e4730a1caf402898f6607f84af45 (patch) | |
tree | f7866f31d53503b333a10f573140efc15d506344 | |
parent | 3bc172a41c43ebe3d81caf4d75f10cfb48218006 (diff) | |
download | haskell-8e79e2a973c1e4730a1caf402898f6607f84af45.tar.gz |
Unconditionally flush update remembered set during minor GCwip/gc/optimize
Flush the update remembered set. The goal here is to flush periodically to
ensure that we don't end up with a thread who marks their stack on their
local update remembered set and doesn't flush until the nonmoving sync
period as this would result in a large fraction of the heap being marked
during the sync pause.
-rw-r--r-- | rts/sm/GC.c | 8 | ||||
-rw-r--r-- | rts/sm/NonMovingMark.c | 43 |
2 files changed, 51 insertions, 0 deletions
diff --git a/rts/sm/GC.c b/rts/sm/GC.c index 6be81e5ff0..83e9c97bd9 100644 --- a/rts/sm/GC.c +++ b/rts/sm/GC.c @@ -730,6 +730,14 @@ GarbageCollect (uint32_t collect_gen, } } // for all generations + // Flush the update remembered set. See Note [Eager update remembered set + // flushing] in NonMovingMark.c + if (RtsFlags.GcFlags.useNonmoving) { + RELEASE_SM_LOCK; + nonmovingAddUpdRemSetBlocks(&gct->cap->upd_rem_set.queue); + ACQUIRE_SM_LOCK; + } + // Mark and sweep the oldest generation. // N.B. This can only happen after we've moved // oldest_gen->scavenged_large_objects back to oldest_gen->large_objects. diff --git a/rts/sm/NonMovingMark.c b/rts/sm/NonMovingMark.c index eeccd58428..2ab4572447 100644 --- a/rts/sm/NonMovingMark.c +++ b/rts/sm/NonMovingMark.c @@ -131,6 +131,49 @@ StgIndStatic *debug_caf_list_snapshot = (StgIndStatic*)END_OF_CAF_LIST; * The mark phase is responsible for freeing update remembered set block * allocations. * + * Note that we eagerly flush update remembered sets during minor GCs as + * described in Note [Eager update remembered set flushing]. + * + * + * Note [Eager update remembered set flushing] + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * We eagerly flush update remembered sets during minor GCs to avoid scenarios + * like the following which could result in long sync pauses: + * + * 1. We start a major GC, all thread stacks are added to the mark queue. + * 2. The concurrent mark thread starts. + * 3. The mutator is allowed to resume. One mutator thread T is scheduled and marks its + * stack to its local update remembered set. + * 4. The mark thread eventually encounters the mutator thread's stack but + * sees that it has already been marked; skips it. + * 5. Thread T continues running but does not push enough to its update + * remembered set to require a flush. + * 6. Eventually the mark thread finished marking and requests a final sync. + * 7. The thread T flushes its update remembered set. + * 8. We find that a large fraction of the heap (namely the subset that is + * only reachable from the thread T's stack) needs to be marked, incurring + * a large sync pause + * + * We avoid this by periodically (during minor GC) forcing a flush of the + * update remembered set. + * + * A better (but more complex) approach that would be worthwhile trying in the + * future would be to rather do the following: + * + * 1. Concurrent mark phase is on-going + * 2. Mark thread runs out of things to mark + * 3. Mark thread sends a signal to capabilities requesting that they send + * their update remembered sets without suspending their execution + * 4. The mark thread marks everything it was sent; runs out of things to mark + * 5. Mark thread initiates a sync + * 6. Capabilities send their final update remembered sets and suspend execution + * 7. Mark thread marks everything is was sent + * 8. Mark thead allows capabilities to resume. + * + * However, this is obviously a fair amount of complexity and so far the + * periodic eager flushing approach has been sufficient. + * * * Note [Concurrent read barrier on deRefWeak#] * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |