summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Gamari <ben@smart-cactus.org>2019-05-08 21:28:35 -0400
committerBen Gamari <ben@smart-cactus.org>2019-10-22 12:18:39 -0400
commit8e79e2a973c1e4730a1caf402898f6607f84af45 (patch)
treef7866f31d53503b333a10f573140efc15d506344
parent3bc172a41c43ebe3d81caf4d75f10cfb48218006 (diff)
downloadhaskell-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.c8
-rw-r--r--rts/sm/NonMovingMark.c43
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#]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~