1
0
mirror of https://github.com/golang/go synced 2024-10-02 06:18:32 -06:00

runtime: schedule fractional workers on all Ps

Currently only a single P can run a fractional mark worker at a time.
This doesn't let us spread out the load, so it gets concentrated on
whatever unlucky P picks up the token to run a fractional worker. This
can significantly delay goroutines on that P.

This commit changes this scheduling rule so each P separately
schedules fractional workers. This can significantly reduce the load
on any individual P and allows workers to self-preempt earlier. It
does have the downside that it's possible for all Ps to be in
fractional workers simultaneously (an effect STW).

Updates #21698.

Change-Id: Ia1e300c422043fa62bb4e3dd23c6232d81e4419c
Reviewed-on: https://go-review.googlesource.com/68574
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rick Hudson <rlh@golang.org>
This commit is contained in:
Austin Clements 2017-10-05 12:16:45 -04:00
parent 28e1a8e47a
commit e09dbaa1de
2 changed files with 30 additions and 49 deletions

View File

@ -396,23 +396,18 @@ type gcControllerState struct {
assistBytesPerWork float64 assistBytesPerWork float64
// fractionalUtilizationGoal is the fraction of wall clock // fractionalUtilizationGoal is the fraction of wall clock
// time that should be spent in the fractional mark worker. // time that should be spent in the fractional mark worker on
// For example, if the overall mark utilization goal is 25% // each P that isn't running a dedicated worker.
// and GOMAXPROCS is 6, one P will be a dedicated mark worker //
// and this will be set to 0.5 so that 50% of the time some P // For example, if the utilization goal is 25% and there are
// is in a fractional mark worker. This is computed at the // no dedicated workers, this will be 0.25. If there goal is
// beginning of each cycle. // 25%, there is one dedicated worker, and GOMAXPROCS is 5,
// this will be 0.05 to make up the missing 5%.
//
// If this is zero, no fractional workers are needed.
fractionalUtilizationGoal float64 fractionalUtilizationGoal float64
_ [sys.CacheLineSize]byte _ [sys.CacheLineSize]byte
// fractionalMarkWorkersNeeded is the number of fractional
// mark workers that need to be started. This is either 0 or
// 1. This is potentially updated atomically at every
// scheduling point (hence it gets its own cache line).
fractionalMarkWorkersNeeded int64
_ [sys.CacheLineSize]byte
} }
// startCycle resets the GC controller's state and computes estimates // startCycle resets the GC controller's state and computes estimates
@ -471,19 +466,15 @@ func (c *gcControllerState) startCycle() {
// Too many dedicated workers. // Too many dedicated workers.
c.dedicatedMarkWorkersNeeded-- c.dedicatedMarkWorkersNeeded--
} }
c.fractionalUtilizationGoal = totalUtilizationGoal - float64(c.dedicatedMarkWorkersNeeded) c.fractionalUtilizationGoal = (totalUtilizationGoal - float64(c.dedicatedMarkWorkersNeeded)) / float64(gomaxprocs)
} else { } else {
c.fractionalUtilizationGoal = 0 c.fractionalUtilizationGoal = 0
} }
if c.fractionalUtilizationGoal > 0 {
c.fractionalMarkWorkersNeeded = 1
} else {
c.fractionalMarkWorkersNeeded = 0
}
// Clear per-P state // Clear per-P state
for _, p := range allp { for _, p := range allp {
p.gcAssistTime = 0 p.gcAssistTime = 0
p.gcFractionalMarkTime = 0
} }
// Compute initial values for controls that are updated // Compute initial values for controls that are updated
@ -496,7 +487,7 @@ func (c *gcControllerState) startCycle() {
work.initialHeapLive>>20, "->", work.initialHeapLive>>20, "->",
memstats.next_gc>>20, " MB)", memstats.next_gc>>20, " MB)",
" workers=", c.dedicatedMarkWorkersNeeded, " workers=", c.dedicatedMarkWorkersNeeded,
"+", c.fractionalMarkWorkersNeeded, "\n") "+", c.fractionalUtilizationGoal, "\n")
} }
} }
@ -702,31 +693,20 @@ func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g {
// This P is now dedicated to marking until the end of // This P is now dedicated to marking until the end of
// the concurrent mark phase. // the concurrent mark phase.
_p_.gcMarkWorkerMode = gcMarkWorkerDedicatedMode _p_.gcMarkWorkerMode = gcMarkWorkerDedicatedMode
} else { } else if c.fractionalUtilizationGoal == 0 {
if !decIfPositive(&c.fractionalMarkWorkersNeeded) { // No need for fractional workers.
// No more workers are need right now.
return nil return nil
} } else {
// Is this P behind on the fractional utilization
// This P has picked the token for the fractional worker. // goal?
// Is the GC currently under or at the utilization goal?
// If so, do more work.
// //
// This should be kept in sync with pollFractionalWorkerExit. // This should be kept in sync with pollFractionalWorkerExit.
delta := nanotime() - gcController.markStartTime
// TODO(austin): We could fast path this and basically if delta > 0 && float64(_p_.gcFractionalMarkTime)/float64(delta) > c.fractionalUtilizationGoal {
// eliminate contention on c.fractionalMarkWorkersNeeded by // Nope. No need to run a fractional worker.
// precomputing the minimum time at which it's worth
// next scheduling the fractional worker. Then Ps
// don't have to fight in the window where we've
// passed that deadline and no one has started the
// worker yet.
delta := nanotime() - c.markStartTime
if delta > 0 && float64(c.fractionalMarkTime)/float64(delta) > c.fractionalUtilizationGoal {
// Nope, we'd overshoot the utilization goal
atomic.Xaddint64(&c.fractionalMarkWorkersNeeded, +1)
return nil return nil
} }
// Run a fractional worker.
_p_.gcMarkWorkerMode = gcMarkWorkerFractionalMode _p_.gcMarkWorkerMode = gcMarkWorkerFractionalMode
} }
@ -751,8 +731,7 @@ func pollFractionalWorkerExit() bool {
return true return true
} }
p := getg().m.p.ptr() p := getg().m.p.ptr()
// Account for time since starting this worker. selfTime := p.gcFractionalMarkTime + (now - p.gcMarkWorkerStartTime)
selfTime := gcController.fractionalMarkTime + (now - p.gcMarkWorkerStartTime)
// Add some slack to the utilization goal so that the // Add some slack to the utilization goal so that the
// fractional worker isn't behind again the instant it exits. // fractional worker isn't behind again the instant it exits.
return float64(selfTime)/float64(delta) > 1.2*gcController.fractionalUtilizationGoal return float64(selfTime)/float64(delta) > 1.2*gcController.fractionalUtilizationGoal
@ -1387,7 +1366,8 @@ top:
// TODO(austin): Should dedicated workers keep an eye on this // TODO(austin): Should dedicated workers keep an eye on this
// and exit gcDrain promptly? // and exit gcDrain promptly?
atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, -0xffffffff) atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, -0xffffffff)
atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, -0xffffffff) prevFractionalGoal := gcController.fractionalUtilizationGoal
gcController.fractionalUtilizationGoal = 0
if !gcBlackenPromptly { if !gcBlackenPromptly {
// Transition from mark 1 to mark 2. // Transition from mark 1 to mark 2.
@ -1430,7 +1410,7 @@ top:
// Now we can start up mark 2 workers. // Now we can start up mark 2 workers.
atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 0xffffffff) atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 0xffffffff)
atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, 0xffffffff) gcController.fractionalUtilizationGoal = prevFractionalGoal
incnwait := atomic.Xadd(&work.nwait, +1) incnwait := atomic.Xadd(&work.nwait, +1)
if incnwait == work.nproc && !gcMarkWorkAvailable(nil) { if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
@ -1849,7 +1829,7 @@ func gcBgMarkWorker(_p_ *p) {
atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 1) atomic.Xaddint64(&gcController.dedicatedMarkWorkersNeeded, 1)
case gcMarkWorkerFractionalMode: case gcMarkWorkerFractionalMode:
atomic.Xaddint64(&gcController.fractionalMarkTime, duration) atomic.Xaddint64(&gcController.fractionalMarkTime, duration)
atomic.Xaddint64(&gcController.fractionalMarkWorkersNeeded, 1) atomic.Xaddint64(&_p_.gcFractionalMarkTime, duration)
case gcMarkWorkerIdleMode: case gcMarkWorkerIdleMode:
atomic.Xaddint64(&gcController.idleMarkTime, duration) atomic.Xaddint64(&gcController.idleMarkTime, duration)
} }

View File

@ -523,6 +523,7 @@ type p struct {
// Per-P GC state // Per-P GC state
gcAssistTime int64 // Nanoseconds in assistAlloc gcAssistTime int64 // Nanoseconds in assistAlloc
gcFractionalMarkTime int64 // Nanoseconds in fractional mark worker
gcBgMarkWorker guintptr gcBgMarkWorker guintptr
gcMarkWorkerMode gcMarkWorkerMode gcMarkWorkerMode gcMarkWorkerMode