Skip to content

Commit fde10c4

Browse files
mknyszekgopherbot
authored andcommitted
runtime: split gcMarkWorkAvailable into two separate conditions
Right now, gcMarkWorkAvailable is used in two scenarios. The first is critical: we use it to determine whether we're approaching mark termination, and it's crucial to reaching a fixed point across the ragged barrier in gcMarkDone. The second is a heuristic: should we spin up another GC worker? This change splits gcMarkWorkAvailable into these two separate conditions. This change also deduplicates the logic for updating work.nwait into more abstract helpers "gcBeginWork" and "gcEndWork." This change is solely refactoring, and should be a no-op. There are only two functional changes: - work.nwait is incremented after setting pp.gcMarkWorkerMode in the background worker code. I don't believe this change is observable except if the code fails to update work.nwait (either it results in a non-sensical number, or the stack is corrupted) in which case the goroutine may not be labeled as a mark worker in the resulting stack trace (it should be obvious from the stack frames though). - endCheckmarks also checks work.nwait == work.nproc, which should be fine since we never mutate work.nwait on that path. That extra check should be a no-op. Splitting these two use-cases for gcMarkWorkAvailable is conceptually helpful, and the checks may also diverge from Green Tea once we get rid of the global span queue. Change-Id: I0bec244a14ee82919c4deb7c1575589c0dca1089 Reviewed-on: https://go-review.googlesource.com/c/go/+/701176 Reviewed-by: Michael Pratt <[email protected]> Auto-Submit: Michael Knyszek <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 5d040df commit fde10c4

File tree

5 files changed

+50
-42
lines changed

5 files changed

+50
-42
lines changed

src/runtime/mcheckmark.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func startCheckmarks() {
6868

6969
// endCheckmarks ends the checkmarks phase.
7070
func endCheckmarks() {
71-
if gcMarkWorkAvailable(nil) {
71+
if !gcIsMarkDone() {
7272
throw("GC work not flushed")
7373
}
7474
useCheckmark = false

src/runtime/mgc.go

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -869,10 +869,11 @@ var gcDebugMarkDone struct {
869869
// all local work to the global queues where it can be discovered by
870870
// other workers.
871871
//
872+
// All goroutines performing GC work must call gcBeginWork to signal
873+
// that they're executing GC work. They must call gcEndWork when done.
872874
// This should be called when all local mark work has been drained and
873-
// there are no remaining workers. Specifically, when
874-
//
875-
// work.nwait == work.nproc && !gcMarkWorkAvailable(p)
875+
// there are no remaining workers. Specifically, when gcEndWork returns
876+
// true.
876877
//
877878
// The calling context must be preemptible.
878879
//
@@ -896,7 +897,7 @@ top:
896897
// empty before performing the ragged barrier. Otherwise,
897898
// there could be global work that a P could take after the P
898899
// has passed the ragged barrier.
899-
if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) {
900+
if !(gcphase == _GCmark && gcIsMarkDone()) {
900901
semrelease(&work.markDoneSema)
901902
return
902903
}
@@ -1514,11 +1515,7 @@ func gcBgMarkWorker(ready chan struct{}) {
15141515
trackLimiterEvent = pp.limiterEvent.start(limiterEventIdleMarkWork, startTime)
15151516
}
15161517

1517-
decnwait := atomic.Xadd(&work.nwait, -1)
1518-
if decnwait == work.nproc {
1519-
println("runtime: work.nwait=", decnwait, "work.nproc=", work.nproc)
1520-
throw("work.nwait was > work.nproc")
1521-
}
1518+
gcBeginWork()
15221519

15231520
systemstack(func() {
15241521
// Mark our goroutine preemptible so its stack can be scanned or observed
@@ -1570,15 +1567,6 @@ func gcBgMarkWorker(ready chan struct{}) {
15701567
atomic.Xaddint64(&pp.gcFractionalMarkTime, duration)
15711568
}
15721569

1573-
// Was this the last worker and did we run out
1574-
// of work?
1575-
incnwait := atomic.Xadd(&work.nwait, +1)
1576-
if incnwait > work.nproc {
1577-
println("runtime: p.gcMarkWorkerMode=", pp.gcMarkWorkerMode,
1578-
"work.nwait=", incnwait, "work.nproc=", work.nproc)
1579-
throw("work.nwait > work.nproc")
1580-
}
1581-
15821570
// We'll releasem after this point and thus this P may run
15831571
// something else. We must clear the worker mode to avoid
15841572
// attributing the mode to a different (non-worker) G in
@@ -1587,7 +1575,7 @@ func gcBgMarkWorker(ready chan struct{}) {
15871575

15881576
// If this worker reached a background mark completion
15891577
// point, signal the main GC goroutine.
1590-
if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
1578+
if gcEndWork() {
15911579
// We don't need the P-local buffers here, allow
15921580
// preemption because we may schedule like a regular
15931581
// goroutine in gcMarkDone (block on locks, etc).
@@ -1599,13 +1587,44 @@ func gcBgMarkWorker(ready chan struct{}) {
15991587
}
16001588
}
16011589

1602-
// gcMarkWorkAvailable reports whether executing a mark worker
1603-
// on p is potentially useful. p may be nil, in which case it only
1604-
// checks the global sources of work.
1605-
func gcMarkWorkAvailable(p *p) bool {
1590+
// gcShouldScheduleWorker reports whether executing a mark worker
1591+
// on p is potentially useful. p may be nil.
1592+
func gcShouldScheduleWorker(p *p) bool {
16061593
if p != nil && !p.gcw.empty() {
16071594
return true
16081595
}
1596+
return gcMarkWorkAvailable()
1597+
}
1598+
1599+
// gcIsMarkDone reports whether the mark phase is (probably) done.
1600+
func gcIsMarkDone() bool {
1601+
return work.nwait == work.nproc && !gcMarkWorkAvailable()
1602+
}
1603+
1604+
// gcBeginWork signals to the garbage collector that a new worker is
1605+
// about to process GC work.
1606+
func gcBeginWork() {
1607+
decnwait := atomic.Xadd(&work.nwait, -1)
1608+
if decnwait == work.nproc {
1609+
println("runtime: work.nwait=", decnwait, "work.nproc=", work.nproc)
1610+
throw("work.nwait was > work.nproc")
1611+
}
1612+
}
1613+
1614+
// gcEndWork signals to the garbage collector that a new worker has just finished
1615+
// its work. It reports whether it was the last worker and there's no more work
1616+
// to do. If it returns true, the caller must call gcMarkDone.
1617+
func gcEndWork() (last bool) {
1618+
incnwait := atomic.Xadd(&work.nwait, +1)
1619+
if incnwait > work.nproc {
1620+
println("runtime: work.nwait=", incnwait, "work.nproc=", work.nproc)
1621+
throw("work.nwait > work.nproc")
1622+
}
1623+
return incnwait == work.nproc && !gcMarkWorkAvailable()
1624+
}
1625+
1626+
// gcMarkWorkAvailable reports whether there's any non-local work available to do.
1627+
func gcMarkWorkAvailable() bool {
16091628
if !work.full.empty() || !work.spanq.empty() {
16101629
return true // global work available
16111630
}

src/runtime/mgcmark.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -675,11 +675,7 @@ func gcAssistAlloc1(gp *g, scanWork int64) {
675675
startTime := nanotime()
676676
trackLimiterEvent := gp.m.p.ptr().limiterEvent.start(limiterEventMarkAssist, startTime)
677677

678-
decnwait := atomic.Xadd(&work.nwait, -1)
679-
if decnwait == work.nproc {
680-
println("runtime: work.nwait =", decnwait, "work.nproc=", work.nproc)
681-
throw("nwait > work.nprocs")
682-
}
678+
gcBeginWork()
683679

684680
// gcDrainN requires the caller to be preemptible.
685681
casGToWaitingForSuspendG(gp, _Grunning, waitReasonGCAssistMarking)
@@ -702,14 +698,7 @@ func gcAssistAlloc1(gp *g, scanWork int64) {
702698

703699
// If this is the last worker and we ran out of work,
704700
// signal a completion point.
705-
incnwait := atomic.Xadd(&work.nwait, +1)
706-
if incnwait > work.nproc {
707-
println("runtime: work.nwait=", incnwait,
708-
"work.nproc=", work.nproc)
709-
throw("work.nwait > work.nproc")
710-
}
711-
712-
if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
701+
if gcEndWork() {
713702
// This has reached a background completion point. Set
714703
// gp.param to a non-nil value to indicate this. It
715704
// doesn't matter what we set it to (it just has to be

src/runtime/mgcpacer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -767,8 +767,8 @@ func (c *gcControllerState) findRunnableGCWorker(pp *p, now int64) (*g, int64) {
767767
gcCPULimiter.update(now)
768768
}
769769

770-
if !gcMarkWorkAvailable(pp) {
771-
// No work to be done right now. This can happen at
770+
if !gcShouldScheduleWorker(pp) {
771+
// No good reason to schedule a worker. This can happen at
772772
// the end of the mark phase when there are still
773773
// assists tapering off. Don't bother running a worker
774774
// now because it'll just return immediately.

src/runtime/proc.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3125,7 +3125,7 @@ func handoffp(pp *p) {
31253125
return
31263126
}
31273127
// if it has GC work, start it straight away
3128-
if gcBlackenEnabled != 0 && gcMarkWorkAvailable(pp) {
3128+
if gcBlackenEnabled != 0 && gcShouldScheduleWorker(pp) {
31293129
startm(pp, false, false)
31303130
return
31313131
}
@@ -3506,7 +3506,7 @@ top:
35063506
//
35073507
// If we're in the GC mark phase, can safely scan and blacken objects,
35083508
// and have work to do, run idle-time marking rather than give up the P.
3509-
if gcBlackenEnabled != 0 && gcMarkWorkAvailable(pp) && gcController.addIdleMarkWorker() {
3509+
if gcBlackenEnabled != 0 && gcShouldScheduleWorker(pp) && gcController.addIdleMarkWorker() {
35103510
node := (*gcBgMarkWorkerNode)(gcBgMarkWorkerPool.pop())
35113511
if node != nil {
35123512
pp.gcMarkWorkerMode = gcMarkWorkerIdleMode
@@ -3913,7 +3913,7 @@ func checkIdleGCNoP() (*p, *g) {
39133913
if atomic.Load(&gcBlackenEnabled) == 0 || !gcController.needIdleMarkWorker() {
39143914
return nil, nil
39153915
}
3916-
if !gcMarkWorkAvailable(nil) {
3916+
if !gcShouldScheduleWorker(nil) {
39173917
return nil, nil
39183918
}
39193919

0 commit comments

Comments
 (0)