]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: implement GC scan work estimator
authorAustin Clements <austin@google.com>
Thu, 12 Mar 2015 21:56:14 +0000 (17:56 -0400)
committerAustin Clements <austin@google.com>
Tue, 21 Apr 2015 15:35:04 +0000 (15:35 +0000)
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 <rlh@golang.org>
src/runtime/mgc.go
src/runtime/mstats.go

index f6787ab5f829ea16ebe10154d29615cdc381baad..2fe013294d81316908be15afd002c6b03bf0f9c3 100644 (file)
@@ -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
index 568a2ba4a9ff75b76558906a26ee779c52d59e70..36931fb6b48b2091d273e8214f8db66613015449 100644 (file)
@@ -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