Commit 4e9fc0df authored by Austin Clements's avatar Austin Clements

runtime: implement GC scan work estimator

This implements tracking the scan work ratio of a GC cycle and using
this to estimate the scan work that will be required by the next GC
cycle. Currently this estimate is unused; it will be used to drive
mutator assists.

Change-Id: I8685b59d89cf1d83eddfc9b30d84da4e3a7f4b72
Reviewed-on: https://go-review.googlesource.com/8833Reviewed-by: default avatarRick Hudson <rlh@golang.org>
parent 571ebae6
...@@ -180,7 +180,14 @@ func setGCPercent(in int32) (out int32) { ...@@ -180,7 +180,14 @@ func setGCPercent(in int32) (out int32) {
// utilization between assist and background marking to be 25% of // utilization between assist and background marking to be 25% of
// GOMAXPROCS. The high-level design of this algorithm is documented // GOMAXPROCS. The high-level design of this algorithm is documented
// at http://golang.org/s/go15gcpacing. // at http://golang.org/s/go15gcpacing.
var gcController gcControllerState var gcController = gcControllerState{
// Initial work ratio guess.
//
// TODO(austin): This is based on the work ratio of the
// compiler on ./all.bash. Run a wider variety of programs and
// see what their work ratios are.
workRatioAvg: 0.5 / float64(ptrSize),
}
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
...@@ -188,11 +195,44 @@ type gcControllerState struct { ...@@ -188,11 +195,44 @@ type gcControllerState struct {
// batched arbitrarily, since the value is only read at the // batched arbitrarily, since the value is only read at the
// end of the cycle. // end of the cycle.
scanWork int64 scanWork int64
// workRatioAvg is a moving average of the scan work ratio
// (scan work per byte marked).
workRatioAvg float64
} }
// startCycle resets the GC controller's state. // startCycle resets the GC controller's state and computes estimates
// for a new GC cycle.
func (c *gcControllerState) startCycle() { func (c *gcControllerState) startCycle() {
c.scanWork = 0 c.scanWork = 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
// the appropriate growth from heap_marked, even though the
// real heap_marked may not have a meaningful value (on the
// first cycle) or may be much smaller (resulting in a large
// error response).
if memstats.next_gc <= heapminimum {
memstats.heap_marked = uint64(float64(memstats.next_gc) / (1 + float64(gcpercent)/100))
}
// Compute the expected work based on last cycle's marked bytes.
// (Currently unused)
scanWorkExpected := uint64(float64(memstats.heap_marked) * c.workRatioAvg)
_ = scanWorkExpected
}
// endCycle updates the GC controller state at the end of the
// concurrent part of the GC cycle.
func (c *gcControllerState) endCycle() {
// EWMA weight given to this cycle's scan work ratio.
const workRatioWeight = 0.75
// Compute the scan work ratio for this cycle.
workRatio := float64(c.scanWork) / float64(work.bytesMarked)
// Update EWMA of recent scan work ratios.
c.workRatioAvg = workRatioWeight*workRatio + (1-workRatioWeight)*c.workRatioAvg
} }
// Determine whether to initiate a GC. // Determine whether to initiate a GC.
...@@ -418,6 +458,8 @@ func gc(mode int) { ...@@ -418,6 +458,8 @@ func gc(mode int) {
// The gcphase is _GCmark, it will transition to _GCmarktermination // The gcphase is _GCmark, it will transition to _GCmarktermination
// below. The important thing is that the wb remains active until // below. The important thing is that the wb remains active until
// all marking is complete. This includes writes made by the GC. // all marking is complete. This includes writes made by the GC.
gcController.endCycle()
} else { } else {
// For non-concurrent GC (mode != gcBackgroundMode) // For non-concurrent GC (mode != gcBackgroundMode)
// The g stacks have not been scanned so clear g state // The g stacks have not been scanned so clear g state
...@@ -632,6 +674,7 @@ func gcMark(start_time int64) { ...@@ -632,6 +674,7 @@ func gcMark(start_time int64) {
// Trigger the next GC cycle when the allocated heap has // Trigger the next GC cycle when the allocated heap has
// reached 7/8ths of the growth allowed by gcpercent. // reached 7/8ths of the growth allowed by gcpercent.
memstats.heap_live = work.bytesMarked memstats.heap_live = work.bytesMarked
memstats.heap_marked = work.bytesMarked
memstats.next_gc = memstats.heap_live + (memstats.heap_live*uint64(gcpercent)/100)*7/8 memstats.next_gc = memstats.heap_live + (memstats.heap_live*uint64(gcpercent)/100)*7/8
if memstats.next_gc < heapminimum { if memstats.next_gc < heapminimum {
memstats.next_gc = heapminimum memstats.next_gc = heapminimum
......
...@@ -68,6 +68,12 @@ type mstats struct { ...@@ -68,6 +68,12 @@ type mstats struct {
// since then. heap_live <= heap_alloc, since heap_live // since then. heap_live <= heap_alloc, since heap_live
// excludes unmarked objects that have not yet been swept. // excludes unmarked objects that have not yet been swept.
heap_live uint64 heap_live uint64
// heap_marked is the number of bytes marked by the previous
// GC. After mark termination, heap_live == heap_marked, but
// unlike heap_live, heap_marked does not change until the
// next mark termination.
heap_marked uint64
} }
var memstats mstats var memstats mstats
......
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