Commit 29be3f19 authored by Austin Clements's avatar Austin Clements

runtime: generalize GC trigger

Currently the GC triggering condition is an awkward combination of the
gcMode (whether or not it's gcBackgroundMode) and a boolean
"forceTrigger" flag.

Replace this with a new gcTrigger type that represents the range of
transition predicates we need. This has several advantages:

1. We can remove the awkward logic that affects the trigger behavior
   based on the gcMode. Now gcMode purely controls whether to run a
   STW GC or not and the gcTrigger controls whether this is a forced
   GC that cannot be consolidated with other GC cycles.

2. We can lift the time-based triggering logic in sysmon to just
   another type of GC trigger and move the logic to the trigger test.

3. This sets us up to have a cycle count-based trigger, which we'll
   use to make runtime.GC trigger concurrent GC with the desired
   consolidation properties.

For #18216.

Change-Id: If9cd49349579a548800f5022ae47b8128004bbfc
Reviewed-on: https://go-review.googlesource.com/37516
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarRick Hudson <rlh@golang.org>
parent 640cd3b3
...@@ -764,8 +764,10 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { ...@@ -764,8 +764,10 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
assistG.gcAssistBytes -= int64(size - dataSize) assistG.gcAssistBytes -= int64(size - dataSize)
} }
if shouldhelpgc && gcShouldStart(false) { if shouldhelpgc {
gcStart(gcBackgroundMode, false) if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(gcBackgroundMode, t)
}
} }
return x return x
......
...@@ -888,7 +888,7 @@ var work struct { ...@@ -888,7 +888,7 @@ var work struct {
// garbage collection is complete. It may also block the entire // garbage collection is complete. It may also block the entire
// program. // program.
func GC() { func GC() {
gcStart(gcForceBlockMode, false) gcStart(gcForceBlockMode, gcTrigger{kind: gcTriggerAlways})
} }
// gcMode indicates how concurrent a GC cycle should be. // gcMode indicates how concurrent a GC cycle should be.
...@@ -900,24 +900,62 @@ const ( ...@@ -900,24 +900,62 @@ const (
gcForceBlockMode // stop-the-world GC now and STW sweep (forced by user) gcForceBlockMode // stop-the-world GC now and STW sweep (forced by user)
) )
// gcShouldStart returns true if the exit condition for the _GCoff // A gcTrigger is a predicate for starting a GC cycle. Specifically,
// phase has been met. The exit condition should be tested when // it is an exit condition for the _GCoff phase.
// allocating. type gcTrigger struct {
// kind gcTriggerKind
// If forceTrigger is true, it ignores the current heap size, but now int64 // gcTriggerTime: current time
// checks all other conditions. In general this should be false. }
func gcShouldStart(forceTrigger bool) bool {
return gcphase == _GCoff && (forceTrigger || memstats.heap_live >= memstats.gc_trigger) && memstats.enablegc && panicking == 0 && gcpercent >= 0 type gcTriggerKind int
const (
// gcTriggerAlways indicates that a cycle should be started
// unconditionally, even if GOGC is off. This cannot be
// consolidated with other cycles.
gcTriggerAlways gcTriggerKind = iota
// gcTriggerHeap indicates that a cycle should be started when
// the heap size reaches the trigger heap size computed by the
// controller.
gcTriggerHeap
// gcTriggerTime indicates that a cycle should be started when
// it's been more than forcegcperiod nanoseconds since the
// previous GC cycle.
gcTriggerTime
)
// test returns true if the trigger condition is satisfied, meaning
// that the exit condition for the _GCoff phase has been met. The exit
// condition should be tested when allocating.
func (t gcTrigger) test() bool {
if !(gcphase == _GCoff && memstats.enablegc && panicking == 0) {
return false
}
if t.kind == gcTriggerAlways {
return true
}
if gcpercent < 0 {
return false
}
switch t.kind {
case gcTriggerHeap:
return memstats.heap_live >= memstats.gc_trigger
case gcTriggerTime:
lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime))
return lastgc != 0 && t.now-lastgc > forcegcperiod
}
return true
} }
// gcStart transitions the GC from _GCoff to _GCmark (if mode == // gcStart transitions the GC from _GCoff to _GCmark (if
// gcBackgroundMode) or _GCmarktermination (if mode != // !mode.stwMark) or _GCmarktermination (if mode.stwMark) by
// gcBackgroundMode) by performing sweep termination and GC // performing sweep termination and GC initialization.
// initialization.
// //
// This may return without performing this transition in some cases, // This may return without performing this transition in some cases,
// such as when called on a system stack or with locks held. // such as when called on a system stack or with locks held.
func gcStart(mode gcMode, forceTrigger bool) { func gcStart(mode gcMode, trigger gcTrigger) {
// Since this is called from malloc and malloc is called in // Since this is called from malloc and malloc is called in
// the guts of a number of libraries that might be holding // the guts of a number of libraries that might be holding
// locks, don't attempt to start GC in non-preemptible or // locks, don't attempt to start GC in non-preemptible or
...@@ -940,7 +978,7 @@ func gcStart(mode gcMode, forceTrigger bool) { ...@@ -940,7 +978,7 @@ func gcStart(mode gcMode, forceTrigger bool) {
// //
// We check the transition condition continuously here in case // We check the transition condition continuously here in case
// this G gets delayed in to the next GC cycle. // this G gets delayed in to the next GC cycle.
for (mode != gcBackgroundMode || gcShouldStart(forceTrigger)) && gosweepone() != ^uintptr(0) { for trigger.test() && gosweepone() != ^uintptr(0) {
sweep.nbgsweep++ sweep.nbgsweep++
} }
...@@ -951,18 +989,18 @@ func gcStart(mode gcMode, forceTrigger bool) { ...@@ -951,18 +989,18 @@ func gcStart(mode gcMode, forceTrigger bool) {
// or re-check the transition condition because we // or re-check the transition condition because we
// specifically *don't* want to share the transition with // specifically *don't* want to share the transition with
// another thread. // another thread.
useStartSema := mode == gcBackgroundMode useStartSema := trigger.kind != gcTriggerAlways
if useStartSema { if useStartSema {
semacquire(&work.startSema) semacquire(&work.startSema)
// Re-check transition condition under transition lock. // Re-check transition condition under transition lock.
if !gcShouldStart(forceTrigger) { if !trigger.test() {
semrelease(&work.startSema) semrelease(&work.startSema)
return return
} }
} }
// For stats, check if this GC was forced by the user. // For stats, check if this GC was forced by the user.
forced := mode != gcBackgroundMode forced := trigger.kind == gcTriggerAlways
// In gcstoptheworld debug mode, upgrade the mode accordingly. // In gcstoptheworld debug mode, upgrade the mode accordingly.
// We do this after re-checking the transition condition so // We do this after re-checking the transition condition so
......
...@@ -1062,7 +1062,7 @@ func (h *mheap) scavenge(k int32, now, limit uint64) { ...@@ -1062,7 +1062,7 @@ func (h *mheap) scavenge(k int32, now, limit uint64) {
//go:linkname runtime_debug_freeOSMemory runtime/debug.freeOSMemory //go:linkname runtime_debug_freeOSMemory runtime/debug.freeOSMemory
func runtime_debug_freeOSMemory() { func runtime_debug_freeOSMemory() {
gcStart(gcForceBlockMode, false) gcStart(gcForceBlockMode, gcTrigger{kind: gcTriggerAlways})
systemstack(func() { mheap_.scavenge(-1, ^uint64(0), 0) }) systemstack(func() { mheap_.scavenge(-1, ^uint64(0), 0) })
} }
......
...@@ -228,7 +228,8 @@ func forcegchelper() { ...@@ -228,7 +228,8 @@ func forcegchelper() {
if debug.gctrace > 0 { if debug.gctrace > 0 {
println("GC forced") println("GC forced")
} }
gcStart(gcBackgroundMode, true) // Time-triggered, fully concurrent.
gcStart(gcBackgroundMode, gcTrigger{kind: gcTriggerTime, now: nanotime()})
} }
} }
...@@ -3790,8 +3791,7 @@ func sysmon() { ...@@ -3790,8 +3791,7 @@ func sysmon() {
idle++ idle++
} }
// check if we need to force a GC // check if we need to force a GC
lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime)) if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {
if gcShouldStart(true) && lastgc != 0 && now-lastgc > forcegcperiod && atomic.Load(&forcegc.idle) != 0 {
lock(&forcegc.lock) lock(&forcegc.lock)
forcegc.idle = 0 forcegc.idle = 0
forcegc.g.schedlink = 0 forcegc.g.schedlink = 0
......
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