mirror of
https://github.com/golang/go
synced 2024-11-23 20:30:04 -07:00
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/8834 Reviewed-by: Rick Hudson <rlh@golang.org>
This commit is contained in:
parent
4e9fc0df48
commit
8e24283a28
@ -196,6 +196,13 @@ type gcControllerState struct {
|
||||
// end of the cycle.
|
||||
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
|
||||
// (scan work per byte marked).
|
||||
workRatioAvg float64
|
||||
@ -205,6 +212,7 @@ type gcControllerState struct {
|
||||
// for a new GC cycle.
|
||||
func (c *gcControllerState) startCycle() {
|
||||
c.scanWork = 0
|
||||
c.bgScanCredit = 0
|
||||
|
||||
// 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
|
||||
@ -235,6 +243,13 @@ func (c *gcControllerState) endCycle() {
|
||||
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.
|
||||
// If the GC is already working no need to trigger another one.
|
||||
// This should establish a feedback loop where if the GC does not
|
||||
@ -440,7 +455,7 @@ func gc(mode int) {
|
||||
tMark = nanotime()
|
||||
}
|
||||
var gcw gcWork
|
||||
gcDrain(&gcw)
|
||||
gcDrain(&gcw, gcBgCreditSlack)
|
||||
gcw.dispose()
|
||||
// Despite the barrier in gcDrain, gcDrainNs may still
|
||||
// be doing work at this point. This is okay because
|
||||
@ -649,7 +664,7 @@ func gcMark(start_time int64) {
|
||||
gchelperstart()
|
||||
parfordo(work.markfor)
|
||||
var gcw gcWork
|
||||
gcDrain(&gcw)
|
||||
gcDrain(&gcw, -1)
|
||||
gcw.dispose()
|
||||
|
||||
if work.full != 0 {
|
||||
@ -831,7 +846,7 @@ func gchelper() {
|
||||
parfordo(work.markfor)
|
||||
if gcphase != _GCscan {
|
||||
var gcw gcWork
|
||||
gcDrain(&gcw) // blocks in getfull
|
||||
gcDrain(&gcw, -1) // blocks in getfull
|
||||
gcw.dispose()
|
||||
}
|
||||
|
||||
|
@ -354,12 +354,23 @@ func scanframeworker(frame *stkframe, unused unsafe.Pointer, gcw *gcWork) {
|
||||
|
||||
// gcDrain scans objects in work buffers, blackening grey
|
||||
// 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
|
||||
func gcDrain(gcw *gcWork) {
|
||||
func gcDrain(gcw *gcWork, flushScanCredit int64) {
|
||||
if gcphase != _GCmark && gcphase != _GCmarktermination {
|
||||
throw("scanblock phase incorrect")
|
||||
}
|
||||
|
||||
var lastScanFlush, nextScanFlush int64
|
||||
if flushScanCredit != -1 {
|
||||
lastScanFlush = gcw.scanWork
|
||||
nextScanFlush = lastScanFlush + flushScanCredit
|
||||
} else {
|
||||
nextScanFlush = int64(^uint64(0) >> 1)
|
||||
}
|
||||
|
||||
for {
|
||||
// If another proc wants a pointer, give it some.
|
||||
if work.nwait > 0 && work.full == 0 {
|
||||
@ -378,6 +389,20 @@ func gcDrain(gcw *gcWork) {
|
||||
// into an empty wbuf in scanobject so there could be
|
||||
// a performance hit as we keep fetching fresh wbufs.
|
||||
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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user