Commit c99d7f7f authored by Austin Clements's avatar Austin Clements

runtime: decentralize mark done and mark termination

This moves all of the mark 1 to mark 2 transition and mark termination
to the mark done transition function. This means these transitions are
now handled on the goroutine that detected mark completion. This also
means that the GC coordinator and the background completion barriers
are no longer used and various workarounds to yield to the coordinator
are no longer necessary. These will be removed in follow-up commits.

One consequence of this is that mark workers now need to be
preemptible when performing the mark done transition. This allows them
to stop the world and to perform the final clean-up steps of GC after
restarting the world. They are only made preemptible while performing
this transition, so if the worker findRunnableGCWorker would schedule
isn't available, we didn't want to schedule it anyway.

Fixes #11970.

Change-Id: I9203a2d6287eeff62d589ec02ad9cb1e29ddb837
Reviewed-on: https://go-review.googlesource.com/16391Reviewed-by: default avatarRick Hudson <rlh@golang.org>
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent d986bf27
...@@ -742,8 +742,7 @@ func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer { ...@@ -742,8 +742,7 @@ func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer {
// down this G's allocation and help the GC stay // down this G's allocation and help the GC stay
// scheduled by yielding. // scheduled by yielding.
// //
// TODO: This is a workaround. Either help the GC make // TODO: This is unused. Remove.
// the transition or block.
gp := getg() gp := getg()
if gp != gp.m.g0 && gp.m.locks == 0 && gp.m.preemptoff == "" { if gp != gp.m.g0 && gp.m.locks == 0 && gp.m.preemptoff == "" {
Gosched() Gosched()
......
...@@ -166,6 +166,7 @@ func gcinit() { ...@@ -166,6 +166,7 @@ func gcinit() {
} }
memstats.next_gc = heapminimum memstats.next_gc = heapminimum
work.startSema = 1 work.startSema = 1
work.markDoneSema = 1
} }
func readgogc() int32 { func readgogc() int32 {
...@@ -615,14 +616,9 @@ func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g { ...@@ -615,14 +616,9 @@ func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g {
throw("gcControllerState.findRunnable: blackening not enabled") throw("gcControllerState.findRunnable: blackening not enabled")
} }
if _p_.gcBgMarkWorker == nil { if _p_.gcBgMarkWorker == nil {
throw("gcControllerState.findRunnable: no background mark worker") // The mark worker associated with this P is blocked
} // performing a mark transition. We can't run it
if work.bgMark1.done != 0 && work.bgMark2.done != 0 { // because it may be on some other run or wait queue.
// Background mark is done. Don't schedule background
// mark worker any more. (This is not just an
// optimization. Without this we can spin scheduling
// the background worker and having it return
// immediately with no work to do.)
return nil return nil
} }
...@@ -821,12 +817,16 @@ var work struct { ...@@ -821,12 +817,16 @@ var work struct {
// startSema protects the transition from "off" to mark or // startSema protects the transition from "off" to mark or
// mark termination. // mark termination.
startSema uint32 startSema uint32
// markDoneSema protects transitions from mark 1 to mark 2 and
// from mark 2 to mark termination.
markDoneSema uint32
bgMarkReady note // signal background mark worker has started bgMarkReady note // signal background mark worker has started
bgMarkDone uint32 // cas to 1 when at a background mark completion point bgMarkDone uint32 // cas to 1 when at a background mark completion point
// Background mark completion signaling // Background mark completion signaling
// Coordination for the 2 parts of the mark phase. // Coordination for the 2 parts of the mark phase.
// TODO(austin): Unused. Remove.
bgMark1 bgMarkSignal bgMark1 bgMarkSignal
bgMark2 bgMarkSignal bgMark2 bgMarkSignal
...@@ -894,8 +894,7 @@ const ( ...@@ -894,8 +894,7 @@ const (
// startGCCoordinator starts and readies the GC coordinator goroutine. // startGCCoordinator starts and readies the GC coordinator goroutine.
// //
// TODO(austin): This function is temporary and will go away when we // TODO(austin): This function unused. Remove it and backgroundgc.
// finish the transition to the decentralized state machine.
func startGCCoordinator() { func startGCCoordinator() {
// trigger concurrent GC // trigger concurrent GC
readied := false readied := false
...@@ -932,7 +931,7 @@ var bggc struct { ...@@ -932,7 +931,7 @@ var bggc struct {
// bggc holds the state of the backgroundgc. // bggc holds the state of the backgroundgc.
func backgroundgc() { func backgroundgc() {
for { for {
gc(gcBackgroundMode) gcMarkTermination()
lock(&bggc.lock) lock(&bggc.lock)
bggc.working-- bggc.working--
unlock(&bggc.lock) unlock(&bggc.lock)
...@@ -1089,26 +1088,23 @@ func gcStart(mode gcMode, forceTrigger bool) { ...@@ -1089,26 +1088,23 @@ func gcStart(mode gcMode, forceTrigger bool) {
// mutators. // mutators.
atomicstore(&gcBlackenEnabled, 1) atomicstore(&gcBlackenEnabled, 1)
// Assists and workers can start the moment we start
// the world.
gcController.assistStartTime = now
gcController.bgMarkStartTime = now
// Concurrent mark. // Concurrent mark.
systemstack(startTheWorldWithSema) systemstack(startTheWorldWithSema)
now = nanotime() now = nanotime()
work.pauseNS += now - work.pauseStart work.pauseNS += now - work.pauseStart
gcController.assistStartTime = now
work.tMark = now work.tMark = now
// Enable background mark workers.
gcController.bgMarkStartTime = now
work.bgMark1.clear()
// TODO: Make mark 1 completion handle the transition.
startGCCoordinator()
} else { } else {
t := nanotime() t := nanotime()
work.tMark, work.tMarkTerm = t, t work.tMark, work.tMarkTerm = t, t
work.heapGoal = work.heap0 work.heapGoal = work.heap0
// Perform mark termination. This will restart the world. // Perform mark termination. This will restart the world.
gc(mode) gcMarkTermination()
} }
if useStartSema { if useStartSema {
...@@ -1125,60 +1121,74 @@ func gcStart(mode gcMode, forceTrigger bool) { ...@@ -1125,60 +1121,74 @@ func gcStart(mode gcMode, forceTrigger bool) {
// work may still be cached in per-P work buffers. In mark 2, per-P // work may still be cached in per-P work buffers. In mark 2, per-P
// caches are disabled. // caches are disabled.
func gcMarkDone() { func gcMarkDone() {
// TODO(austin): This should perform the transition rather semacquire(&work.markDoneSema, false)
// than handing it off to the coordinator.
if gcBlackenPromptly {
if work.bgMark1.done == 0 {
throw("completing mark 2, but bgMark1.done == 0")
}
work.bgMark2.complete()
} else {
work.bgMark1.complete()
}
}
func gc(mode gcMode) {
// If mode == gcBackgroundMode, world is not stopped.
// If mode != gcBackgroundMode, world is stopped.
// TODO(austin): This is temporary.
if mode == gcBackgroundMode { // Re-check transition condition under transition lock.
// Wait for background mark completion. if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) {
work.bgMark1.wait() semrelease(&work.markDoneSema)
return
}
gcMarkRootCheck() // Disallow starting new workers so that any remaining workers
// in the current mark phase will drain out.
//
// TODO(austin): Should dedicated workers keep an eye on this
// and exit gcDrain promptly?
xaddint64(&gcController.dedicatedMarkWorkersNeeded, -0xffffffff)
xaddint64(&gcController.fractionalMarkWorkersNeeded, -0xffffffff)
if !gcBlackenPromptly {
// Transition from mark 1 to mark 2.
//
// The global work list is empty, but there can still be work // The global work list is empty, but there can still be work
// sitting in the per-P work caches and there can be more // sitting in the per-P work caches and there can be more
// objects reachable from global roots since they don't have write // objects reachable from global roots since they don't have write
// barriers. Rescan some roots and flush work caches. // barriers. Rescan some roots and flush work caches.
systemstack(func() {
// Disallow caching workbufs.
gcBlackenPromptly = true
// Flush all currently cached workbufs. This gcMarkRootCheck()
// also forces any remaining background
// workers out of their loop. // Disallow caching workbufs and indicate that we're in mark 2.
gcBlackenPromptly = true
// Prevent completion of mark 2 until we've flushed
// cached workbufs.
xadd(&work.nwait, -1)
// Rescan global data and BSS. There may still work
// workers running at this point, so bump "jobs" down
// before "next" so they won't try running root jobs
// until we set next.
atomicstore(&work.markrootJobs, uint32(fixedRootCount+work.nDataRoots+work.nBSSRoots))
atomicstore(&work.markrootNext, fixedRootCount)
// GC is set up for mark 2. Let Gs blocked on the
// transition lock go while we flush caches.
semrelease(&work.markDoneSema)
systemstack(func() {
// Flush all currently cached workbufs and
// ensure all Ps see gcBlackenPromptly. This
// also blocks until any remaining mark 1
// workers have exited their loop so we can
// start new mark 2 workers that will observe
// the new root marking jobs.
forEachP(func(_p_ *p) { forEachP(func(_p_ *p) {
_p_.gcw.dispose() _p_.gcw.dispose()
}) })
// Rescan global data and BSS. Bump "jobs"
// down before "next" so workers won't try
// running root jobs until we set "next".
//
// This also ensures there will be queued mark
// work, which ensures some mark worker will
// run and signal mark 2 completion.
atomicstore(&work.markrootJobs, uint32(fixedRootCount+work.nDataRoots+work.nBSSRoots))
atomicstore(&work.markrootNext, fixedRootCount)
}) })
// Wait for this more aggressive background mark to complete. // Now we can start up mark 2 workers.
work.bgMark2.clear() xaddint64(&gcController.dedicatedMarkWorkersNeeded, 0xffffffff)
work.bgMark2.wait() xaddint64(&gcController.fractionalMarkWorkersNeeded, 0xffffffff)
// Begin mark termination. incnwait := xadd(&work.nwait, +1)
if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
// This recursion is safe because the call
// can't take this same "if" branch.
gcMarkDone()
}
} else {
// Transition to mark termination.
now := nanotime() now := nanotime()
work.tMarkTerm = now work.tMarkTerm = now
work.pauseStart = now work.pauseStart = now
...@@ -1200,9 +1210,19 @@ func gc(mode gcMode) { ...@@ -1200,9 +1210,19 @@ func gc(mode gcMode) {
// start the world again. // start the world again.
gcWakeAllAssists() gcWakeAllAssists()
// Likewise, release the transition lock. Blocked
// workers and assists will run when we start the
// world again.
semrelease(&work.markDoneSema)
gcController.endCycle() gcController.endCycle()
// Perform mark termination. This will restart the world.
gcMarkTermination()
} }
}
func gcMarkTermination() {
// World is stopped. // World is stopped.
// Start marktermination which includes enabling the write barrier. // Start marktermination which includes enabling the write barrier.
atomicstore(&gcBlackenEnabled, 0) atomicstore(&gcBlackenEnabled, 0)
...@@ -1411,13 +1431,13 @@ func gcBgMarkPrepare() { ...@@ -1411,13 +1431,13 @@ func gcBgMarkPrepare() {
func gcBgMarkWorker(p *p) { func gcBgMarkWorker(p *p) {
// Register this G as the background mark worker for p. // Register this G as the background mark worker for p.
if p.gcBgMarkWorker != nil { casgp := func(gpp **g, old, new *g) bool {
throw("P already has a background mark worker") return casp((*unsafe.Pointer)(unsafe.Pointer(gpp)), unsafe.Pointer(old), unsafe.Pointer(new))
} }
gp := getg()
gp := getg()
mp := acquirem() mp := acquirem()
p.gcBgMarkWorker = gp owned := casgp(&p.gcBgMarkWorker, nil, gp)
// After this point, the background mark worker is scheduled // After this point, the background mark worker is scheduled
// cooperatively by gcController.findRunnable. Hence, it must // cooperatively by gcController.findRunnable. Hence, it must
// never be preempted, as this would put it into _Grunnable // never be preempted, as this would put it into _Grunnable
...@@ -1425,6 +1445,13 @@ func gcBgMarkWorker(p *p) { ...@@ -1425,6 +1445,13 @@ func gcBgMarkWorker(p *p) {
// is set, this puts itself into _Gwaiting to be woken up by // is set, this puts itself into _Gwaiting to be woken up by
// gcController.findRunnable at the appropriate time. // gcController.findRunnable at the appropriate time.
notewakeup(&work.bgMarkReady) notewakeup(&work.bgMarkReady)
if !owned {
// A sleeping worker came back and reassociated with
// the P. That's fine.
releasem(mp)
return
}
for { for {
// Go to sleep until woken by gcContoller.findRunnable. // Go to sleep until woken by gcContoller.findRunnable.
// We can't releasem yet since even the call to gopark // We can't releasem yet since even the call to gopark
...@@ -1502,7 +1529,25 @@ func gcBgMarkWorker(p *p) { ...@@ -1502,7 +1529,25 @@ func gcBgMarkWorker(p *p) {
// If this worker reached a background mark completion // If this worker reached a background mark completion
// point, signal the main GC goroutine. // point, signal the main GC goroutine.
if incnwait == work.nproc && !gcMarkWorkAvailable(nil) { if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
// Make this G preemptible and disassociate it
// as the worker for this P so
// findRunnableGCWorker doesn't try to
// schedule it.
p.gcBgMarkWorker = nil
releasem(mp)
gcMarkDone() gcMarkDone()
// Disable preemption and reassociate with the P.
//
// We may be running on a different P at this
// point, so this has to be done carefully.
mp = acquirem()
if !casgp(&p.gcBgMarkWorker, nil, gp) {
// The P got a new worker.
releasem(mp)
break
}
} }
} }
} }
......
...@@ -409,7 +409,6 @@ retry: ...@@ -409,7 +409,6 @@ retry:
if incnwait == work.nproc && !gcMarkWorkAvailable(nil) { if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
// This has reached a background completion // This has reached a background completion
// point. // point.
gcMarkDone()
completed = true completed = true
} }
duration := nanotime() - startTime duration := nanotime() - startTime
...@@ -422,9 +421,7 @@ retry: ...@@ -422,9 +421,7 @@ retry:
}) })
if completed { if completed {
// We called complete() above, so we should yield to gcMarkDone()
// the now-runnable GC coordinator.
Gosched()
} }
if gp.gcAssistBytes < 0 { if gp.gcAssistBytes < 0 {
...@@ -452,7 +449,7 @@ retry: ...@@ -452,7 +449,7 @@ retry:
lock(&work.assistQueue.lock) lock(&work.assistQueue.lock)
// If the GC cycle is over, just return. This is the // If the GC cycle is over, just return. This is the
// likely path if we called Gosched above. We do this // likely path if we completed above. We do this
// under the lock to prevent a GC cycle from ending // under the lock to prevent a GC cycle from ending
// between this check and queuing the assist. // between this check and queuing the assist.
if atomicload(&gcBlackenEnabled) == 0 { if atomicload(&gcBlackenEnabled) == 0 {
......
...@@ -1057,6 +1057,8 @@ func mstart1() { ...@@ -1057,6 +1057,8 @@ func mstart1() {
// memory barrier. GC uses this as a "ragged barrier." // memory barrier. GC uses this as a "ragged barrier."
// //
// The caller must hold worldsema. // The caller must hold worldsema.
//
//go:systemstack
func forEachP(fn func(*p)) { func forEachP(fn func(*p)) {
mp := acquirem() mp := acquirem()
_p_ := getg().m.p.ptr() _p_ := getg().m.p.ptr()
...@@ -1115,6 +1117,8 @@ func forEachP(fn func(*p)) { ...@@ -1115,6 +1117,8 @@ func forEachP(fn func(*p)) {
for { for {
// Wait for 100us, then try to re-preempt in // Wait for 100us, then try to re-preempt in
// case of any races. // case of any races.
//
// Requires system stack.
if notetsleep(&sched.safePointNote, 100*1000) { if notetsleep(&sched.safePointNote, 100*1000) {
noteclear(&sched.safePointNote) noteclear(&sched.safePointNote)
break break
...@@ -1773,10 +1777,9 @@ top: ...@@ -1773,10 +1777,9 @@ top:
stop: stop:
// We have nothing to do. If we're in the GC mark phase, can // We have nothing to do. If we're in the GC mark phase, can
// safely scan and blacken objects, can start a worker, and // safely scan and blacken objects, and have work to do, run
// have work to do, run idle-time marking rather than give up // idle-time marking rather than give up the P.
// the P. if _p_ := _g_.m.p.ptr(); gcBlackenEnabled != 0 && _p_.gcBgMarkWorker != nil && gcMarkWorkAvailable(_p_) {
if _p_ := _g_.m.p.ptr(); gcBlackenEnabled != 0 && _p_.gcBgMarkWorker != nil && (work.bgMark1.done == 0 || work.bgMark2.done == 0) && gcMarkWorkAvailable(_p_) {
_p_.gcMarkWorkerMode = gcMarkWorkerIdleMode _p_.gcMarkWorkerMode = gcMarkWorkerIdleMode
gp := _p_.gcBgMarkWorker gp := _p_.gcBgMarkWorker
casgstatus(gp, _Gwaiting, _Grunnable) casgstatus(gp, _Gwaiting, _Grunnable)
......
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