Commit 1678b2c5 authored by Austin Clements's avatar Austin Clements

runtime: implement STW GC in terms of concurrent GC

Currently, STW GC works very differently from concurrent GC. The
largest differences in that in concurrent GC, all marking work is done
by background mark workers during the mark phase, while in STW GC, all
marking work is done by gchelper during the mark termination phase.

This is a consequence of the evolution of Go's GC from a STW GC by
incrementally moving work from STW mark termination into concurrent
mark. However, at this point, the STW code paths exist only as a
debugging mode. Having separate code paths for this increases the
maintenance burden and complexity of the garbage collector. At the
same time, these code paths aren't tested nearly as well, making it
far more likely that they will bit-rot.

This CL reverses the relationship between STW GC, by re-implementing
STW GC in terms of concurrent GC.

This builds on the new scheduled support for disabling user goroutine
scheduling. During sweep termination, it disables user scheduling, so
when the GC starts the world again for concurrent mark, it's really
only "concurrent" with itself.

There are several code paths that were specific to STW GC that are now
vestigial. We'll remove these in the follow-up CLs.

Updates #26903.

Change-Id: Ia3883d2fcf7ab1d89bdc9c8ee54bf9bffb32c096
Reviewed-on: https://go-review.googlesource.com/c/134780
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarRick Hudson <rlh@golang.org>
parent 6e9fb11b
...@@ -455,6 +455,12 @@ func (c *gcControllerState) startCycle() { ...@@ -455,6 +455,12 @@ func (c *gcControllerState) startCycle() {
c.fractionalUtilizationGoal = 0 c.fractionalUtilizationGoal = 0
} }
// In STW mode, we just want dedicated workers.
if debug.gcstoptheworld > 0 {
c.dedicatedMarkWorkersNeeded = int64(gomaxprocs)
c.fractionalUtilizationGoal = 0
}
// Clear per-P state // Clear per-P state
for _, p := range allp { for _, p := range allp {
p.gcAssistTime = 0 p.gcAssistTime = 0
...@@ -1264,9 +1270,7 @@ func gcStart(trigger gcTrigger) { ...@@ -1264,9 +1270,7 @@ func gcStart(trigger gcTrigger) {
traceGCStart() traceGCStart()
} }
if mode == gcBackgroundMode { gcBgMarkStartWorkers()
gcBgMarkStartWorkers()
}
gcResetMarkState() gcResetMarkState()
...@@ -1296,65 +1300,65 @@ func gcStart(trigger gcTrigger) { ...@@ -1296,65 +1300,65 @@ func gcStart(trigger gcTrigger) {
clearpools() clearpools()
work.cycles++ work.cycles++
if mode == gcBackgroundMode { // Do as much work concurrently as possible
gcController.startCycle()
work.heapGoal = memstats.next_gc
// Enter concurrent mark phase and enable gcController.startCycle()
// write barriers. work.heapGoal = memstats.next_gc
//
// Because the world is stopped, all Ps will // In STW mode, disable scheduling of user Gs. This may also
// observe that write barriers are enabled by // disable scheduling of this goroutine, so it may block as
// the time we start the world and begin // soon as we start the world again.
// scanning. if mode != gcBackgroundMode {
// schedEnableUser(false)
// Write barriers must be enabled before assists are }
// enabled because they must be enabled before
// any non-leaf heap objects are marked. Since // Enter concurrent mark phase and enable
// allocations are blocked until assists can // write barriers.
// happen, we want enable assists as early as //
// possible. // Because the world is stopped, all Ps will
setGCPhase(_GCmark) // observe that write barriers are enabled by
// the time we start the world and begin
gcBgMarkPrepare() // Must happen before assist enable. // scanning.
gcMarkRootPrepare() //
// Write barriers must be enabled before assists are
// Mark all active tinyalloc blocks. Since we're // enabled because they must be enabled before
// allocating from these, they need to be black like // any non-leaf heap objects are marked. Since
// other allocations. The alternative is to blacken // allocations are blocked until assists can
// the tiny block on every allocation from it, which // happen, we want enable assists as early as
// would slow down the tiny allocator. // possible.
gcMarkTinyAllocs() setGCPhase(_GCmark)
// At this point all Ps have enabled the write gcBgMarkPrepare() // Must happen before assist enable.
// barrier, thus maintaining the no white to gcMarkRootPrepare()
// black invariant. Enable mutator assists to
// put back-pressure on fast allocating // Mark all active tinyalloc blocks. Since we're
// mutators. // allocating from these, they need to be black like
atomic.Store(&gcBlackenEnabled, 1) // other allocations. The alternative is to blacken
// the tiny block on every allocation from it, which
// Assists and workers can start the moment we start // would slow down the tiny allocator.
// the world. gcMarkTinyAllocs()
gcController.markStartTime = now
// At this point all Ps have enabled the write
// Concurrent mark. // barrier, thus maintaining the no white to
systemstack(func() { // black invariant. Enable mutator assists to
now = startTheWorldWithSema(trace.enabled) // put back-pressure on fast allocating
}) // mutators.
atomic.Store(&gcBlackenEnabled, 1)
// Assists and workers can start the moment we start
// the world.
gcController.markStartTime = now
// Concurrent mark.
systemstack(func() {
now = startTheWorldWithSema(trace.enabled)
work.pauseNS += now - work.pauseStart work.pauseNS += now - work.pauseStart
work.tMark = now work.tMark = now
} else { })
if trace.enabled { // In STW mode, we could block the instant systemstack
// Switch to mark termination STW. // returns, so don't do anything important here. Make sure we
traceGCSTWDone() // block rather than returning to user code.
traceGCSTWStart(0) if mode != gcBackgroundMode {
} Gosched()
t := nanotime()
work.tMark, work.tMarkTerm = t, t
work.heapGoal = work.heap0
// Perform mark termination. This will restart the world.
gcMarkTermination(memstats.triggerRatio)
} }
semrelease(&work.startSema) semrelease(&work.startSema)
...@@ -1468,6 +1472,10 @@ top: ...@@ -1468,6 +1472,10 @@ top:
// world again. // world again.
semrelease(&work.markDoneSema) semrelease(&work.markDoneSema)
// In STW mode, re-enable user goroutines. These will be
// queued to run after we start the world.
schedEnableUser(true)
// endCycle depends on all gcWork cache stats being flushed. // endCycle depends on all gcWork cache stats being flushed.
// The termination algorithm above ensured that up to // The termination algorithm above ensured that up to
// allocations since the ragged barrier. // allocations since the ragged barrier.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment