Commit 8e8219de authored by Austin Clements's avatar Austin Clements

runtime: update gcController.scanWork regularly

Currently, gcController.scanWork is updated as lazily as possible
since it is only read at the end of the GC cycle. We're about to read
it during the GC cycle to improve the assist ratio revisions, so
modify gcDrain* to regularly flush to gcController.scanWork in much
the same way as we regularly flush to gcController.bgScanCredit.

One consequence of this is that it's difficult to keep gcw.scanWork
monotonic, so we give up on that and simply return the amount of scan
work done by gcDrainN rather than calculating it in the caller.

Change-Id: I7b50acdc39602f843eed0b5c6d2dacd7e762b81d
Reviewed-on: https://go-review.googlesource.com/15407Reviewed-by: default avatarRick Hudson <rlh@golang.org>
parent c18b163c
...@@ -295,9 +295,9 @@ var gcController = gcControllerState{ ...@@ -295,9 +295,9 @@ var gcController = gcControllerState{
type gcControllerState struct { type gcControllerState struct {
// scanWork is the total scan work performed this cycle. This // scanWork is the total scan work performed this cycle. This
// is updated atomically during the cycle. Updates may be // is updated atomically during the cycle. Updates occur in
// batched arbitrarily, since the value is only read at the // bounded batches, since it is both written and read
// end of the cycle. // throughout the cycle.
// //
// Currently this is the bytes of heap scanned. For most uses, // Currently this is the bytes of heap scanned. For most uses,
// this is an opaque unit of work, but for estimation the // this is an opaque unit of work, but for estimation the
...@@ -682,12 +682,13 @@ func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g { ...@@ -682,12 +682,13 @@ func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g {
// marking as a fraction of GOMAXPROCS. // marking as a fraction of GOMAXPROCS.
const gcGoalUtilization = 0.25 const gcGoalUtilization = 0.25
// gcBgCreditSlack is the amount of scan work credit background // gcCreditSlack is the amount of scan work credit that can can
// scanning can accumulate locally before updating // accumulate locally before updating gcController.scanWork and,
// gcController.bgScanCredit. Lower values give mutator assists more // optionally, gcController.bgScanCredit. Lower values give a more
// accurate accounting of background scanning. Higher values reduce // accurate assist ratio and make it more likely that assists will
// memory contention. // successfully steal background credit. Higher values reduce memory
const gcBgCreditSlack = 2000 // contention.
const gcCreditSlack = 2000
// gcAssistTimeSlack is the nanoseconds of mutator assist time that // gcAssistTimeSlack is the nanoseconds of mutator assist time that
// can accumulate on a P before updating gcController.assistTime. // can accumulate on a P before updating gcController.assistTime.
......
...@@ -290,10 +290,8 @@ retry: ...@@ -290,10 +290,8 @@ retry:
// drain own cached work first in the hopes that it // drain own cached work first in the hopes that it
// will be more cache friendly. // will be more cache friendly.
gcw := &getg().m.p.ptr().gcw gcw := &getg().m.p.ptr().gcw
startScanWork := gcw.scanWork workDone := gcDrainN(gcw, scanWork)
gcDrainN(gcw, scanWork)
// Record that we did this much scan work. // Record that we did this much scan work.
workDone := gcw.scanWork - startScanWork
gp.gcscanwork += workDone gp.gcscanwork += workDone
scanWork -= workDone scanWork -= workDone
// If we are near the end of the mark phase // If we are near the end of the mark phase
...@@ -569,7 +567,7 @@ const ( ...@@ -569,7 +567,7 @@ const (
// workers are blocked in gcDrain. // workers are blocked in gcDrain.
// //
// If flags&gcDrainFlushBgCredit != 0, gcDrain flushes scan work // If flags&gcDrainFlushBgCredit != 0, gcDrain flushes scan work
// credit to gcController.bgScanCredit every gcBgCreditSlack units of // credit to gcController.bgScanCredit every gcCreditSlack units of
// scan work. // scan work.
//go:nowritebarrier //go:nowritebarrier
func gcDrain(gcw *gcWork, flags gcDrainFlags) { func gcDrain(gcw *gcWork, flags gcDrainFlags) {
...@@ -580,13 +578,7 @@ func gcDrain(gcw *gcWork, flags gcDrainFlags) { ...@@ -580,13 +578,7 @@ func gcDrain(gcw *gcWork, flags gcDrainFlags) {
blocking := flags&gcDrainUntilPreempt == 0 blocking := flags&gcDrainUntilPreempt == 0
flushBgCredit := flags&gcDrainFlushBgCredit != 0 flushBgCredit := flags&gcDrainFlushBgCredit != 0
var lastScanFlush, nextScanFlush int64 initScanWork := gcw.scanWork
if flushBgCredit {
lastScanFlush = gcw.scanWork
nextScanFlush = lastScanFlush + gcBgCreditSlack
} else {
nextScanFlush = int64(^uint64(0) >> 1)
}
gp := getg() gp := getg()
for blocking || !gp.preempt { for blocking || !gp.preempt {
...@@ -616,16 +608,23 @@ func gcDrain(gcw *gcWork, flags gcDrainFlags) { ...@@ -616,16 +608,23 @@ func gcDrain(gcw *gcWork, flags gcDrainFlags) {
// Flush background scan work credit to the global // Flush background scan work credit to the global
// account if we've accumulated enough locally so // account if we've accumulated enough locally so
// mutator assists can draw on it. // mutator assists can draw on it.
if gcw.scanWork >= nextScanFlush { if gcw.scanWork >= gcCreditSlack {
credit := gcw.scanWork - lastScanFlush xaddint64(&gcController.scanWork, gcw.scanWork)
xaddint64(&gcController.bgScanCredit, credit) if flushBgCredit {
lastScanFlush = gcw.scanWork xaddint64(&gcController.bgScanCredit, gcw.scanWork-initScanWork)
nextScanFlush = lastScanFlush + gcBgCreditSlack initScanWork = 0
}
gcw.scanWork = 0
} }
} }
if flushBgCredit {
credit := gcw.scanWork - lastScanFlush // Flush remaining scan work credit.
xaddint64(&gcController.bgScanCredit, credit) if gcw.scanWork > 0 {
xaddint64(&gcController.scanWork, gcw.scanWork)
if flushBgCredit {
xaddint64(&gcController.bgScanCredit, gcw.scanWork-initScanWork)
}
gcw.scanWork = 0
} }
} }
...@@ -633,24 +632,42 @@ func gcDrain(gcw *gcWork, flags gcDrainFlags) { ...@@ -633,24 +632,42 @@ func gcDrain(gcw *gcWork, flags gcDrainFlags) {
// scanWork units of scan work. This is best-effort, so it may perform // scanWork units of scan work. This is best-effort, so it may perform
// less work if it fails to get a work buffer. Otherwise, it will // less work if it fails to get a work buffer. Otherwise, it will
// perform at least n units of work, but may perform more because // perform at least n units of work, but may perform more because
// scanning is always done in whole object increments. // scanning is always done in whole object increments. It returns the
// amount of scan work performed.
//go:nowritebarrier //go:nowritebarrier
func gcDrainN(gcw *gcWork, scanWork int64) { func gcDrainN(gcw *gcWork, scanWork int64) int64 {
if !writeBarrierEnabled { if !writeBarrierEnabled {
throw("gcDrainN phase incorrect") throw("gcDrainN phase incorrect")
} }
targetScanWork := gcw.scanWork + scanWork
for gcw.scanWork < targetScanWork { // There may already be scan work on the gcw, which we don't
// want to claim was done by this call.
workFlushed := -gcw.scanWork
for workFlushed+gcw.scanWork < scanWork {
// This might be a good place to add prefetch code... // This might be a good place to add prefetch code...
// if(wbuf.nobj > 4) { // if(wbuf.nobj > 4) {
// PREFETCH(wbuf->obj[wbuf.nobj - 3]; // PREFETCH(wbuf->obj[wbuf.nobj - 3];
// } // }
b := gcw.tryGet() b := gcw.tryGet()
if b == 0 { if b == 0 {
return break
} }
scanobject(b, gcw) scanobject(b, gcw)
// Flush background scan work credit.
if gcw.scanWork >= gcCreditSlack {
xaddint64(&gcController.scanWork, gcw.scanWork)
workFlushed += gcw.scanWork
gcw.scanWork = 0
}
} }
// Unlike gcDrain, there's no need to flush remaining work
// here because this never flushes to bgScanCredit and
// gcw.dispose will flush any remaining work to scanWork.
return workFlushed + gcw.scanWork
} }
// scanblock scans b as scanobject would, but using an explicit // scanblock scans b as scanobject would, but using an explicit
......
...@@ -68,7 +68,7 @@ type gcWork struct { ...@@ -68,7 +68,7 @@ type gcWork struct {
bytesMarked uint64 bytesMarked uint64
// Scan work performed on this gcWork. This is aggregated into // Scan work performed on this gcWork. This is aggregated into
// gcController by dispose. // gcController by dispose and may also be flushed by callers.
scanWork int64 scanWork int64
} }
......
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