]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: make gcSetTriggerRatio work at any time
authorAustin Clements <austin@google.com>
Mon, 3 Apr 2017 19:47:11 +0000 (15:47 -0400)
committerAustin Clements <austin@google.com>
Fri, 21 Apr 2017 17:41:59 +0000 (17:41 +0000)
This changes gcSetTriggerRatio so it can be called even during
concurrent mark or sweep. In this case, it will adjust the pacing of
the current phase, accounting for progress that has already been made.

To make this work for concurrent sweep, this introduces a "basis" for
the pagesSwept count, much like the basis we just introduced for
heap_live. This lets gcSetTriggerRatio shift the basis to the current
heap_live and pagesSwept and compute a slope from there to completion.
This avoids creating a discontinuity where, if the ratio has
increased, there has to be a flurry of sweep activity to catch up.
Instead, this creates a continuous, piece-wise linear function as
adjustments are made.

For #19076.

Change-Id: Ibcd76aeeb81ff4814b00be7cbd3530b73bbdbba9
Reviewed-on: https://go-review.googlesource.com/39833
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rick Hudson <rlh@golang.org>
src/runtime/mgc.go
src/runtime/mgcsweep.go
src/runtime/mheap.go

index 8cba9f72bb135c0412090cbf35ab5b5519f1cc85..8ec062af18f6dc100a5f96c3ebd0937f3850b810 100644 (file)
@@ -491,17 +491,12 @@ func (c *gcControllerState) startCycle() {
 
 // revise updates the assist ratio during the GC cycle to account for
 // improved estimates. This should be called either under STW or
-// whenever memstats.heap_scan or memstats.heap_live is updated (with
-// mheap_.lock held).
+// whenever memstats.heap_scan, memstats.heap_live, or
+// memstats.next_gc is updated (with mheap_.lock held).
 //
 // It should only be called when gcBlackenEnabled != 0 (because this
 // is when assists are enabled and the necessary statistics are
 // available).
-//
-// TODO: Consider removing the periodic controller update altogether.
-// Since we switched to allocating black, in theory we shouldn't have
-// to change the assist ratio. However, this is still a useful hook
-// that we've found many uses for when experimenting.
 func (c *gcControllerState) revise() {
        // Compute the expected scan work remaining.
        //
@@ -757,10 +752,11 @@ func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g {
 }
 
 // gcSetTriggerRatio sets the trigger ratio and updates everything
-// derived from it: the absolute trigger, the heap goal, and sweep
-// pacing.
+// derived from it: the absolute trigger, the heap goal, mark pacing,
+// and sweep pacing.
 //
-// GC must *not* be in the middle of marking or sweeping.
+// This can be called any time. If GC is the in the middle of a
+// concurrent phase, it will adjust the pacing of that phase.
 //
 // This depends on gcpercent, memstats.heap_marked, and
 // memstats.heap_live. These must be up to date.
@@ -830,15 +826,20 @@ func gcSetTriggerRatio(triggerRatio float64) {
                traceNextGC()
        }
 
-       // Compute the sweep pacing.
+       // Update mark pacing.
+       if gcphase != _GCoff {
+               gcController.revise()
+       }
+
+       // Update sweep pacing.
        if gosweepdone() {
                mheap_.sweepPagesPerByte = 0
-               mheap_.pagesSwept = 0
        } else {
                // Concurrent sweep needs to sweep all of the in-use
                // pages by the time the allocated heap reaches the GC
                // trigger. Compute the ratio of in-use pages to sweep
-               // per byte allocated.
+               // per byte allocated, accounting for the fact that
+               // some might already be swept.
                heapLiveBasis := atomic.Load64(&memstats.heap_live)
                heapDistance := int64(trigger) - int64(heapLiveBasis)
                // Add a little margin so rounding errors and
@@ -849,9 +850,18 @@ func gcSetTriggerRatio(triggerRatio float64) {
                        // Avoid setting the sweep ratio extremely high
                        heapDistance = _PageSize
                }
-               mheap_.sweepPagesPerByte = float64(mheap_.pagesInUse) / float64(heapDistance)
-               mheap_.pagesSwept = 0
-               mheap_.sweepHeapLiveBasis = heapLiveBasis
+               pagesSwept := atomic.Load64(&mheap_.pagesSwept)
+               sweepDistancePages := int64(mheap_.pagesInUse) - int64(pagesSwept)
+               if sweepDistancePages <= 0 {
+                       mheap_.sweepPagesPerByte = 0
+               } else {
+                       mheap_.sweepPagesPerByte = float64(sweepDistancePages) / float64(heapDistance)
+                       mheap_.sweepHeapLiveBasis = heapLiveBasis
+                       // Write pagesSweptBasis last, since this
+                       // signals concurrent sweeps to recompute
+                       // their debt.
+                       atomic.Store64(&mheap_.pagesSweptBasis, pagesSwept)
+               }
        }
 }
 
@@ -1972,6 +1982,7 @@ func gcSweep(mode gcMode) {
                // with an empty swept list.
                throw("non-empty swept list")
        }
+       mheap_.pagesSwept = 0
        unlock(&mheap_.lock)
 
        if !_ConcurrentSweep || mode == gcForceBlockMode {
@@ -1979,7 +1990,6 @@ func gcSweep(mode gcMode) {
                // Record that no proportional sweeping has to happen.
                lock(&mheap_.lock)
                mheap_.sweepPagesPerByte = 0
-               mheap_.pagesSwept = 0
                unlock(&mheap_.lock)
                // Sweep all spans eagerly.
                for sweepone() != ^uintptr(0) {
index 8915b398cda086df2d34bdf66c32b7d8a6eed989..102d734c4deb2bb15d1e0fa3ba4dd051daada17d 100644 (file)
@@ -397,14 +397,21 @@ func deductSweepCredit(spanBytes uintptr, callerSweepPages uintptr) {
                traceGCSweepStart()
        }
 
+retry:
+       sweptBasis := atomic.Load64(&mheap_.pagesSweptBasis)
+
        // Fix debt if necessary.
        newHeapLive := uintptr(atomic.Load64(&memstats.heap_live)-mheap_.sweepHeapLiveBasis) + spanBytes
        pagesTarget := int64(mheap_.sweepPagesPerByte*float64(newHeapLive)) - int64(callerSweepPages)
-       for pagesTarget > int64(atomic.Load64(&mheap_.pagesSwept)) {
+       for pagesTarget > int64(atomic.Load64(&mheap_.pagesSwept)-sweptBasis) {
                if gosweepone() == ^uintptr(0) {
                        mheap_.sweepPagesPerByte = 0
                        break
                }
+               if atomic.Load64(&mheap_.pagesSweptBasis) != sweptBasis {
+                       // Sweep pacing changed. Recompute debt.
+                       goto retry
+               }
        }
 
        if trace.enabled {
index 643fc7c50214feb4a4968c876d9bbcda969fe8b0..c2d5b46223050d671daad70f26eab1d58d161a24 100644 (file)
@@ -75,8 +75,26 @@ type mheap struct {
        _ uint32 // align uint64 fields on 32-bit for atomics
 
        // Proportional sweep
+       //
+       // These parameters represent a linear function from heap_live
+       // to page sweep count. The proportional sweep system works to
+       // stay in the black by keeping the current page sweep count
+       // above this line at the current heap_live.
+       //
+       // The line has slope sweepPagesPerByte and passes through a
+       // basis point at (sweepHeapLiveBasis, pagesSweptBasis). At
+       // any given time, the system is at (memstats.heap_live,
+       // pagesSwept) in this space.
+       //
+       // It's important that the line pass through a point we
+       // control rather than simply starting at a (0,0) origin
+       // because that lets us adjust sweep pacing at any time while
+       // accounting for current progress. If we could only adjust
+       // the slope, it would create a discontinuity in debt if any
+       // progress has already been made.
        pagesInUse         uint64  // pages of spans in stats _MSpanInUse; R/W with mheap.lock
        pagesSwept         uint64  // pages swept this cycle; updated atomically
+       pagesSweptBasis    uint64  // pagesSwept to use as the origin of the sweep ratio; updated atomically
        sweepHeapLiveBasis uint64  // value of heap_live to use as the origin of sweep ratio; written with lock, read without
        sweepPagesPerByte  float64 // proportional sweep ratio; written with lock, read without
        // TODO(austin): pagesInUse should be a uintptr, but the 386