From 4e9fc0df4837d6c786fd79344019b25ac4b06eea Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Thu, 12 Mar 2015 17:56:14 -0400 Subject: [PATCH] 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/8833 Reviewed-by: Rick Hudson --- src/runtime/mgc.go | 47 +++++++++++++++++++++++++++++++++++++++++-- src/runtime/mstats.go | 6 ++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/runtime/mgc.go b/src/runtime/mgc.go index f6787ab5f8..2fe013294d 100644 --- a/src/runtime/mgc.go +++ b/src/runtime/mgc.go @@ -180,7 +180,14 @@ func setGCPercent(in int32) (out int32) { // utilization between assist and background marking to be 25% of // GOMAXPROCS. The high-level design of this algorithm is documented // 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 { // scanWork is the total scan work performed this cycle. This @@ -188,11 +195,44 @@ type gcControllerState struct { // batched arbitrarily, since the value is only read at the // end of the cycle. 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() { 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. @@ -418,6 +458,8 @@ func gc(mode int) { // The gcphase is _GCmark, it will transition to _GCmarktermination // below. The important thing is that the wb remains active until // all marking is complete. This includes writes made by the GC. + + gcController.endCycle() } else { // For non-concurrent GC (mode != gcBackgroundMode) // The g stacks have not been scanned so clear g state @@ -632,6 +674,7 @@ func gcMark(start_time int64) { // Trigger the next GC cycle when the allocated heap has // reached 7/8ths of the growth allowed by gcpercent. memstats.heap_live = work.bytesMarked + memstats.heap_marked = work.bytesMarked memstats.next_gc = memstats.heap_live + (memstats.heap_live*uint64(gcpercent)/100)*7/8 if memstats.next_gc < heapminimum { memstats.next_gc = heapminimum diff --git a/src/runtime/mstats.go b/src/runtime/mstats.go index 568a2ba4a9..36931fb6b4 100644 --- a/src/runtime/mstats.go +++ b/src/runtime/mstats.go @@ -68,6 +68,12 @@ type mstats struct { // since then. heap_live <= heap_alloc, since heap_live // excludes unmarked objects that have not yet been swept. 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 -- 2.48.1