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

runtime: track background scan work credit

This tracks scan work done by background GC in a global pool. Mutator
assists will draw on this credit to avoid doing work when background
GC is staying ahead.

Unlike the other GC controller tracking variables, this will be both
written and read throughout the cycle. Hence, we can't arbitrarily
delay updates like we can for scan work and bytes marked. However, we
still want to minimize contention, so this global credit pool is
allowed some error from the "true" amount of credit. Background GC
accumulates credit locally up to a limit and only then flushes to the
global pool. Similarly, mutator assists will draw from the credit pool
in batches.

Change-Id: I1aa4fc604b63bf53d1ee2a967694dffdfc3e255e
Reviewed-on: https://go-review.googlesource.com/8834Reviewed-by: default avatarRick Hudson <rlh@golang.org>
parent 4e9fc0df
...@@ -196,6 +196,13 @@ type gcControllerState struct { ...@@ -196,6 +196,13 @@ type gcControllerState struct {
// end of the cycle. // end of the cycle.
scanWork int64 scanWork int64
// bgScanCredit is the scan work credit accumulated by the
// concurrent background scan. This credit is accumulated by
// the background scan and stolen by mutator assists. This is
// updated atomically. Updates occur in bounded batches, since
// it is both written and read throughout the cycle.
bgScanCredit int64
// workRatioAvg is a moving average of the scan work ratio // workRatioAvg is a moving average of the scan work ratio
// (scan work per byte marked). // (scan work per byte marked).
workRatioAvg float64 workRatioAvg float64
...@@ -205,6 +212,7 @@ type gcControllerState struct { ...@@ -205,6 +212,7 @@ type gcControllerState struct {
// for a new GC cycle. // for a new GC cycle.
func (c *gcControllerState) startCycle() { func (c *gcControllerState) startCycle() {
c.scanWork = 0 c.scanWork = 0
c.bgScanCredit = 0
// If this is the first GC cycle or we're operating on a very // If this is the first GC cycle or we're operating on a very
// small heap, fake heap_marked so it looks like next_gc is // small heap, fake heap_marked so it looks like next_gc is
...@@ -235,6 +243,13 @@ func (c *gcControllerState) endCycle() { ...@@ -235,6 +243,13 @@ func (c *gcControllerState) endCycle() {
c.workRatioAvg = workRatioWeight*workRatio + (1-workRatioWeight)*c.workRatioAvg c.workRatioAvg = workRatioWeight*workRatio + (1-workRatioWeight)*c.workRatioAvg
} }
// gcBgCreditSlack is the amount of scan work credit background
// scanning can accumulate locally before updating
// gcController.bgScanCredit. Lower values give mutator assists more
// accurate accounting of background scanning. Higher values reduce
// memory contention.
const gcBgCreditSlack = 2000
// Determine whether to initiate a GC. // Determine whether to initiate a GC.
// If the GC is already working no need to trigger another one. // If the GC is already working no need to trigger another one.
// This should establish a feedback loop where if the GC does not // This should establish a feedback loop where if the GC does not
...@@ -440,7 +455,7 @@ func gc(mode int) { ...@@ -440,7 +455,7 @@ func gc(mode int) {
tMark = nanotime() tMark = nanotime()
} }
var gcw gcWork var gcw gcWork
gcDrain(&gcw) gcDrain(&gcw, gcBgCreditSlack)
gcw.dispose() gcw.dispose()
// Despite the barrier in gcDrain, gcDrainNs may still // Despite the barrier in gcDrain, gcDrainNs may still
// be doing work at this point. This is okay because // be doing work at this point. This is okay because
...@@ -649,7 +664,7 @@ func gcMark(start_time int64) { ...@@ -649,7 +664,7 @@ func gcMark(start_time int64) {
gchelperstart() gchelperstart()
parfordo(work.markfor) parfordo(work.markfor)
var gcw gcWork var gcw gcWork
gcDrain(&gcw) gcDrain(&gcw, -1)
gcw.dispose() gcw.dispose()
if work.full != 0 { if work.full != 0 {
...@@ -831,7 +846,7 @@ func gchelper() { ...@@ -831,7 +846,7 @@ func gchelper() {
parfordo(work.markfor) parfordo(work.markfor)
if gcphase != _GCscan { if gcphase != _GCscan {
var gcw gcWork var gcw gcWork
gcDrain(&gcw) // blocks in getfull gcDrain(&gcw, -1) // blocks in getfull
gcw.dispose() gcw.dispose()
} }
......
...@@ -354,12 +354,23 @@ func scanframeworker(frame *stkframe, unused unsafe.Pointer, gcw *gcWork) { ...@@ -354,12 +354,23 @@ func scanframeworker(frame *stkframe, unused unsafe.Pointer, gcw *gcWork) {
// gcDrain scans objects in work buffers, blackening grey // gcDrain scans objects in work buffers, blackening grey
// objects until all work buffers have been drained. // objects until all work buffers have been drained.
// If flushScanCredit != -1, gcDrain flushes accumulated scan work
// credit to gcController.bgScanCredit whenever gcw's local scan work
// credit exceeds flushScanCredit.
//go:nowritebarrier //go:nowritebarrier
func gcDrain(gcw *gcWork) { func gcDrain(gcw *gcWork, flushScanCredit int64) {
if gcphase != _GCmark && gcphase != _GCmarktermination { if gcphase != _GCmark && gcphase != _GCmarktermination {
throw("scanblock phase incorrect") throw("scanblock phase incorrect")
} }
var lastScanFlush, nextScanFlush int64
if flushScanCredit != -1 {
lastScanFlush = gcw.scanWork
nextScanFlush = lastScanFlush + flushScanCredit
} else {
nextScanFlush = int64(^uint64(0) >> 1)
}
for { for {
// If another proc wants a pointer, give it some. // If another proc wants a pointer, give it some.
if work.nwait > 0 && work.full == 0 { if work.nwait > 0 && work.full == 0 {
...@@ -378,6 +389,20 @@ func gcDrain(gcw *gcWork) { ...@@ -378,6 +389,20 @@ func gcDrain(gcw *gcWork) {
// into an empty wbuf in scanobject so there could be // into an empty wbuf in scanobject so there could be
// a performance hit as we keep fetching fresh wbufs. // a performance hit as we keep fetching fresh wbufs.
scanobject(b, 0, nil, gcw) scanobject(b, 0, nil, gcw)
// Flush background scan work credit to the global
// account if we've accumulated enough locally so
// mutator assists can draw on it.
if gcw.scanWork >= nextScanFlush {
credit := gcw.scanWork - lastScanFlush
xaddint64(&gcController.bgScanCredit, credit)
lastScanFlush = gcw.scanWork
nextScanFlush = lastScanFlush + flushScanCredit
}
}
if flushScanCredit != -1 {
credit := gcw.scanWork - lastScanFlush
xaddint64(&gcController.bgScanCredit, credit)
} }
checknocurrentwbuf() checknocurrentwbuf()
} }
......
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