summaryrefslogtreecommitdiff
path: root/libgo/go/runtime/mgc.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/runtime/mgc.go')
-rw-r--r--libgo/go/runtime/mgc.go90
1 files changed, 66 insertions, 24 deletions
diff --git a/libgo/go/runtime/mgc.go b/libgo/go/runtime/mgc.go
index 46b7334e910..b0040f96554 100644
--- a/libgo/go/runtime/mgc.go
+++ b/libgo/go/runtime/mgc.go
@@ -139,6 +139,10 @@ const (
_ConcurrentSweep = true
_FinBlockSize = 4 * 1024
+ // debugScanConservative enables debug logging for stack
+ // frames that are scanned conservatively.
+ debugScanConservative = false
+
// sweepMinHeapDistance is a lower bound on the heap distance
// (in bytes) reserved for concurrent sweeping between GC
// cycles.
@@ -231,6 +235,8 @@ func setGCPercent(in int32) (out int32) {
gcSetTriggerRatio(memstats.triggerRatio)
unlock(&mheap_.lock)
})
+ // Pacing changed, so the scavenger should be awoken.
+ wakeScavenger()
// If we just disabled GC, wait for any concurrent GC mark to
// finish so we always return with no GC running.
@@ -490,25 +496,25 @@ func (c *gcControllerState) revise() {
}
live := atomic.Load64(&memstats.heap_live)
- var heapGoal, scanWorkExpected int64
- if live <= memstats.next_gc {
- // We're under the soft goal. Pace GC to complete at
- // next_gc assuming the heap is in steady-state.
- heapGoal = int64(memstats.next_gc)
+ // Assume we're under the soft goal. Pace GC to complete at
+ // next_gc assuming the heap is in steady-state.
+ heapGoal := int64(memstats.next_gc)
- // Compute the expected scan work remaining.
- //
- // This is estimated based on the expected
- // steady-state scannable heap. For example, with
- // GOGC=100, only half of the scannable heap is
- // expected to be live, so that's what we target.
- //
- // (This is a float calculation to avoid overflowing on
- // 100*heap_scan.)
- scanWorkExpected = int64(float64(memstats.heap_scan) * 100 / float64(100+gcpercent))
- } else {
- // We're past the soft goal. Pace GC so that in the
- // worst case it will complete by the hard goal.
+ // Compute the expected scan work remaining.
+ //
+ // This is estimated based on the expected
+ // steady-state scannable heap. For example, with
+ // GOGC=100, only half of the scannable heap is
+ // expected to be live, so that's what we target.
+ //
+ // (This is a float calculation to avoid overflowing on
+ // 100*heap_scan.)
+ scanWorkExpected := int64(float64(memstats.heap_scan) * 100 / float64(100+gcpercent))
+
+ if live > memstats.next_gc || c.scanWork > scanWorkExpected {
+ // We're past the soft goal, or we've already done more scan
+ // work than we expected. Pace GC so that in the worst case it
+ // will complete by the hard goal.
const maxOvershoot = 1.1
heapGoal = int64(float64(memstats.next_gc) * maxOvershoot)
@@ -520,7 +526,7 @@ func (c *gcControllerState) revise() {
//
// Note that we currently count allocations during GC as both
// scannable heap (heap_scan) and scan work completed
- // (scanWork), so allocation will change this difference will
+ // (scanWork), so allocation will change this difference
// slowly in the soft regime and not at all in the hard
// regime.
scanWorkRemaining := scanWorkExpected - c.scanWork
@@ -765,11 +771,25 @@ func gcSetTriggerRatio(triggerRatio float64) {
goal = memstats.heap_marked + memstats.heap_marked*uint64(gcpercent)/100
}
+ // If we let triggerRatio go too low, then if the application
+ // is allocating very rapidly we might end up in a situation
+ // where we're allocating black during a nearly always-on GC.
+ // The result of this is a growing heap and ultimately an
+ // increase in RSS. By capping us at a point >0, we're essentially
+ // saying that we're OK using more CPU during the GC to prevent
+ // this growth in RSS.
+ //
+ // The current constant was chosen empirically: given a sufficiently
+ // fast/scalable allocator with 48 Ps that could drive the trigger ratio
+ // to <0.05, this constant causes applications to retain the same peak
+ // RSS compared to not having this allocator.
+ const minTriggerRatio = 0.6
+
// Set the trigger ratio, capped to reasonable bounds.
- if triggerRatio < 0 {
+ if triggerRatio < minTriggerRatio {
// This can happen if the mutator is allocating very
// quickly or the GC is scanning very slowly.
- triggerRatio = 0
+ triggerRatio = minTriggerRatio
} else if gcpercent >= 0 {
// Ensure there's always a little margin so that the
// mutator assist ratio isn't infinity.
@@ -847,7 +867,8 @@ func gcSetTriggerRatio(triggerRatio float64) {
heapDistance = _PageSize
}
pagesSwept := atomic.Load64(&mheap_.pagesSwept)
- sweepDistancePages := int64(mheap_.pagesInUse) - int64(pagesSwept)
+ pagesInUse := atomic.Load64(&mheap_.pagesInUse)
+ sweepDistancePages := int64(pagesInUse) - int64(pagesSwept)
if sweepDistancePages <= 0 {
mheap_.sweepPagesPerByte = 0
} else {
@@ -1250,6 +1271,7 @@ func gcStart(trigger gcTrigger) {
}
// Ok, we're doing it! Stop everybody else
+ semacquire(&gcsema)
semacquire(&worldsema)
if trace.enabled {
@@ -1348,6 +1370,13 @@ func gcStart(trigger gcTrigger) {
work.pauseNS += now - work.pauseStart
work.tMark = now
})
+
+ // Release the world sema before Gosched() in STW mode
+ // because we will need to reacquire it later but before
+ // this goroutine becomes runnable again, and we could
+ // self-deadlock otherwise.
+ semrelease(&worldsema)
+
// In STW mode, we could block the instant systemstack
// returns, so don't do anything important here. Make sure we
// block rather than returning to user code.
@@ -1417,6 +1446,10 @@ top:
return
}
+ // forEachP needs worldsema to execute, and we'll need it to
+ // stop the world later, so acquire worldsema now.
+ semacquire(&worldsema)
+
// Flush all local buffers and collect flushedWork flags.
gcMarkDoneFlushed = 0
systemstack(func() {
@@ -1477,6 +1510,7 @@ top:
// work to do. Keep going. It's possible the
// transition condition became true again during the
// ragged barrier, so re-check it.
+ semrelease(&worldsema)
goto top
}
@@ -1553,6 +1587,7 @@ top:
now := startTheWorldWithSema(true)
work.pauseNS += now - work.pauseStart
})
+ semrelease(&worldsema)
goto top
}
}
@@ -1651,9 +1686,16 @@ func gcMarkTermination(nextTriggerRatio float64) {
throw("gc done but gcphase != _GCoff")
}
+ // Record next_gc and heap_inuse for scavenger.
+ memstats.last_next_gc = memstats.next_gc
+ memstats.last_heap_inuse = memstats.heap_inuse
+
// Update GC trigger and pacing for the next cycle.
gcSetTriggerRatio(nextTriggerRatio)
+ // Pacing changed, so the scavenger should be awoken.
+ wakeScavenger()
+
// Update timing memstats
now := nanotime()
sec, nsec, _ := time_now()
@@ -1760,6 +1802,7 @@ func gcMarkTermination(nextTriggerRatio float64) {
}
semrelease(&worldsema)
+ semrelease(&gcsema)
// Careful: another GC cycle may start now.
releasem(mp)
@@ -2152,8 +2195,7 @@ func gcResetMarkState() {
// allgs doesn't change.
lock(&allglock)
for _, gp := range allgs {
- gp.gcscandone = false // set to true in gcphasework
- gp.gcscanvalid = false // stack has not been scanned
+ gp.gcscandone = false // set to true in gcphasework
gp.gcAssistBytes = 0
}
unlock(&allglock)