Commit 2b655c0b authored by Russ Cox's avatar Russ Cox

runtime: tidy GC driver

Change-Id: I0da26e89ae73272e49e82c6549c774e5bc97f64c
Reviewed-on: https://go-review.googlesource.com/5331Reviewed-by: default avatarAustin Clements <austin@google.com>
parent 6e70fdde
...@@ -655,7 +655,7 @@ func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer { ...@@ -655,7 +655,7 @@ func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer {
} }
if shouldtriggergc() { if shouldtriggergc() {
gogc(0) startGC(gcBackgroundMode)
} else if shouldhelpgc && atomicloaduint(&bggc.working) == 1 { } else if shouldhelpgc && atomicloaduint(&bggc.working) == 1 {
// bggc.lock not taken since race on bggc.working is benign. // bggc.lock not taken since race on bggc.working is benign.
// At worse we don't call gchelpwork. // At worse we don't call gchelpwork.
......
...@@ -206,9 +206,7 @@ func shouldtriggergc() bool { ...@@ -206,9 +206,7 @@ func shouldtriggergc() bool {
return triggerratio*(int64(memstats.next_gc)-int64(memstats.heap_alloc)) <= int64(memstats.next_gc) && atomicloaduint(&bggc.working) == 0 return triggerratio*(int64(memstats.next_gc)-int64(memstats.heap_alloc)) <= int64(memstats.next_gc) && atomicloaduint(&bggc.working) == 0
} }
var work workdata var work struct {
type workdata struct {
full uint64 // lock-free list of full blocks workbuf full uint64 // lock-free list of full blocks workbuf
empty uint64 // lock-free list of empty blocks workbuf empty uint64 // lock-free list of empty blocks workbuf
partial uint64 // lock-free list of partially filled blocks workbuf partial uint64 // lock-free list of partially filled blocks workbuf
...@@ -226,19 +224,21 @@ type workdata struct { ...@@ -226,19 +224,21 @@ type workdata struct {
// GC runs a garbage collection. // GC runs a garbage collection.
func GC() { func GC() {
gogc(2) startGC(gcForceBlockMode)
} }
// force = 0 - start concurrent GC const (
// force = 1 - do STW GC regardless of current heap usage gcBackgroundMode = iota // concurrent GC
// force = 2 - go STW GC and eager sweep gcForceMode // stop-the-world GC now
func gogc(force int32) { gcForceBlockMode // stop-the-world GC now and wait for sweep
)
func startGC(mode int) {
// The gc is turned off (via enablegc) until the bootstrap has completed. // The gc is turned off (via enablegc) until the bootstrap has completed.
// Also, malloc gets called in the guts of a number of libraries that might be // Also, malloc gets called in the guts of a number of libraries that might be
// holding locks. To avoid deadlocks during stoptheworld, don't bother // holding locks. To avoid deadlocks during stoptheworld, don't bother
// trying to run gc while holding a lock. The next mallocgc without a lock // trying to run gc while holding a lock. The next mallocgc without a lock
// will do the gc instead. // will do the gc instead.
mp := acquirem() mp := acquirem()
if gp := getg(); gp == mp.g0 || mp.locks > 1 || !memstats.enablegc || panicking != 0 || gcpercent < 0 { if gp := getg(); gp == mp.g0 || mp.locks > 1 || !memstats.enablegc || panicking != 0 || gcpercent < 0 {
releasem(mp) releasem(mp)
...@@ -247,7 +247,13 @@ func gogc(force int32) { ...@@ -247,7 +247,13 @@ func gogc(force int32) {
releasem(mp) releasem(mp)
mp = nil mp = nil
if force == 0 { if mode != gcBackgroundMode {
// special synchronous cases
gc(mode)
return
}
// trigger concurrent GC
lock(&bggc.lock) lock(&bggc.lock)
if !bggc.started { if !bggc.started {
bggc.working = 1 bggc.working = 1
...@@ -258,9 +264,6 @@ func gogc(force int32) { ...@@ -258,9 +264,6 @@ func gogc(force int32) {
ready(bggc.g) ready(bggc.g)
} }
unlock(&bggc.lock) unlock(&bggc.lock)
} else {
gcwork(force)
}
} }
// State of the background concurrent GC goroutine. // State of the background concurrent GC goroutine.
...@@ -276,15 +279,15 @@ var bggc struct { ...@@ -276,15 +279,15 @@ var bggc struct {
func backgroundgc() { func backgroundgc() {
bggc.g = getg() bggc.g = getg()
for { for {
gcwork(0) gc(gcBackgroundMode)
lock(&bggc.lock) lock(&bggc.lock)
bggc.working = 0 bggc.working = 0
goparkunlock(&bggc.lock, "Concurrent GC wait", traceEvGoBlock) goparkunlock(&bggc.lock, "Concurrent GC wait", traceEvGoBlock)
} }
} }
func gcwork(force int32) { func gc(mode int) {
// Ok, we're doing it! Stop everybody else
semacquire(&worldsema, false) semacquire(&worldsema, false)
// Pick up the remaining unswept/not being swept spans concurrently // Pick up the remaining unswept/not being swept spans concurrently
...@@ -292,13 +295,11 @@ func gcwork(force int32) { ...@@ -292,13 +295,11 @@ func gcwork(force int32) {
sweep.nbgsweep++ sweep.nbgsweep++
} }
// Ok, we're doing it! Stop everybody else
mp := acquirem() mp := acquirem()
mp.preemptoff = "gcing" mp.preemptoff = "gcing"
releasem(mp) releasem(mp)
gctimer.count++ gctimer.count++
if force == 0 { if mode == gcBackgroundMode {
gctimer.cycle.sweepterm = nanotime() gctimer.cycle.sweepterm = nanotime()
} }
...@@ -307,31 +308,40 @@ func gcwork(force int32) { ...@@ -307,31 +308,40 @@ func gcwork(force int32) {
traceGCStart() traceGCStart()
} }
// Pick up the remaining unswept/not being swept spans before we STW
for gosweepone() != ^uintptr(0) {
sweep.nbgsweep++
}
systemstack(stoptheworld) systemstack(stoptheworld)
systemstack(finishsweep_m) // finish sweep before we start concurrent scan. systemstack(finishsweep_m) // finish sweep before we start concurrent scan.
if force == 0 { // Do as much work concurrently as possible
if mode == gcBackgroundMode { // Do as much work concurrently as possible
systemstack(func() {
gcphase = _GCscan gcphase = _GCscan
systemstack(starttheworld)
// Concurrent scan.
starttheworld()
gctimer.cycle.scan = nanotime() gctimer.cycle.scan = nanotime()
// Do a concurrent heap scan before we stop the world. gcscan_m()
systemstack(gcscan_m)
gctimer.cycle.installmarkwb = nanotime() gctimer.cycle.installmarkwb = nanotime()
systemstack(stoptheworld)
systemstack(gcinstallmarkwb) // Sync.
systemstack(harvestwbufs) stoptheworld()
systemstack(starttheworld) gcphase = _GCmark
harvestwbufs()
// Concurrent mark.
starttheworld()
gctimer.cycle.mark = nanotime() gctimer.cycle.mark = nanotime()
systemstack(gcmark_m) var gcw gcWork
gcDrain(&gcw)
gcw.dispose()
// Begin mark termination.
gctimer.cycle.markterm = nanotime() gctimer.cycle.markterm = nanotime()
systemstack(stoptheworld) stoptheworld()
systemstack(gcinstalloffwb_m) gcphase = _GCoff
})
} else { } else {
// For non-concurrent GC (force != 0) g stack have not been scanned so // For non-concurrent GC (mode != gcBackgroundMode)
// set gcscanvalid such that mark termination scans all stacks. // g stack have not been scanned so set gcscanvalid
// such that mark termination scans all stacks.
// No races here since we are in a STW phase. // No races here since we are in a STW phase.
for _, gp := range allgs { for _, gp := range allgs {
gp.gcworkdone = false // set to true in gcphasework gp.gcworkdone = false // set to true in gcphasework
...@@ -341,9 +351,10 @@ func gcwork(force int32) { ...@@ -341,9 +351,10 @@ func gcwork(force int32) {
startTime := nanotime() startTime := nanotime()
if mp != acquirem() { if mp != acquirem() {
throw("gogc: rescheduled") throw("gcwork: rescheduled")
} }
// TODO(rsc): Should the concurrent GC clear pools earlier?
clearpools() clearpools()
// Run gc on the g0 stack. We do this so that the g stack // Run gc on the g0 stack. We do this so that the g stack
...@@ -355,7 +366,6 @@ func gcwork(force int32) { ...@@ -355,7 +366,6 @@ func gcwork(force int32) {
if debug.gctrace > 1 { if debug.gctrace > 1 {
n = 2 n = 2
} }
eagersweep := force >= 2
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
if i > 0 { if i > 0 {
// refresh start time if doing a second GC // refresh start time if doing a second GC
...@@ -363,12 +373,28 @@ func gcwork(force int32) { ...@@ -363,12 +373,28 @@ func gcwork(force int32) {
} }
// switch to g0, call gc, then switch back // switch to g0, call gc, then switch back
systemstack(func() { systemstack(func() {
gc_m(startTime, eagersweep) gc_m(startTime, mode == gcForceBlockMode)
}) })
} }
systemstack(func() { systemstack(func() {
gccheckmark_m(startTime, eagersweep) // Called from malloc.go using systemstack.
// The world is stopped. Rerun the scan and mark phases
// using the bitMarkedCheck bit instead of the
// bitMarked bit. If the marking encounters an
// bitMarked bit that is not set then we throw.
//go:nowritebarrier
if debug.gccheckmark == 0 {
return
}
if checkmarkphase {
throw("gccheckmark_m, entered with checkmarkphase already true")
}
checkmarkphase = true
initCheckmarks()
gc_m(startTime, mode == gcForceBlockMode) // turns off checkmarkphase + calls clearcheckmarkbits
}) })
if trace.enabled { if trace.enabled {
...@@ -379,13 +405,13 @@ func gcwork(force int32) { ...@@ -379,13 +405,13 @@ func gcwork(force int32) {
// all done // all done
mp.preemptoff = "" mp.preemptoff = ""
if force == 0 { if mode == gcBackgroundMode {
gctimer.cycle.sweep = nanotime() gctimer.cycle.sweep = nanotime()
} }
semrelease(&worldsema) semrelease(&worldsema)
if force == 0 { if mode == gcBackgroundMode {
if gctimer.verbose > 1 { if gctimer.verbose > 1 {
GCprinttimes() GCprinttimes()
} else if gctimer.verbose > 0 { } else if gctimer.verbose > 0 {
...@@ -405,76 +431,23 @@ func gcwork(force int32) { ...@@ -405,76 +431,23 @@ func gcwork(force int32) {
} }
} }
// For now this must be bracketed with a stoptheworld and a starttheworld to ensure // STW is in effect at this point.
// all go routines see the new barrier. //TODO go:nowritebarrier
//go:nowritebarrier func gc_m(start_time int64, eagersweep bool) {
func gcinstalloffwb_m() { if _DebugGCPtrs {
gcphase = _GCoff print("GC start\n")
}
// For now this must be bracketed with a stoptheworld and a starttheworld to ensure
// all go routines see the new barrier.
//go:nowritebarrier
func gcinstallmarkwb() {
gcphase = _GCmark
}
// Mark all objects that are known about.
// This is the concurrent mark phase.
//go:nowritebarrier
func gcmark_m() {
var gcw gcWork
gcDrain(&gcw)
gcw.dispose()
// TODO add another harvestwbuf and reset work.nwait=0, work.ndone=0, and work.nproc=1
// and repeat the above gcDrain.
}
// Called from malloc.go using systemstack.
// The world is stopped. Rerun the scan and mark phases
// using the bitMarkedCheck bit instead of the
// bitMarked bit. If the marking encounters an
// bitMarked bit that is not set then we throw.
//go:nowritebarrier
func gccheckmark_m(startTime int64, eagersweep bool) {
if debug.gccheckmark == 0 {
return
}
if checkmarkphase {
throw("gccheckmark_m, entered with checkmarkphase already true")
} }
checkmarkphase = true
initCheckmarks()
gc_m(startTime, eagersweep) // turns off checkmarkphase + calls clearcheckmarkbits
}
// Called from malloc.go using systemstack, stopping and starting the world handled in caller.
//go:nowritebarrier
func gc_m(start_time int64, eagersweep bool) {
_g_ := getg() _g_ := getg()
gp := _g_.m.curg gp := _g_.m.curg
casgstatus(gp, _Grunning, _Gwaiting) casgstatus(gp, _Grunning, _Gwaiting)
gp.waitreason = "garbage collection" gp.waitreason = "garbage collection"
gc(start_time, eagersweep)
casgstatus(gp, _Gwaiting, _Grunning)
}
// STW is in effect at this point.
//TODO go:nowritebarrier
func gc(start_time int64, eagersweep bool) {
if _DebugGCPtrs {
print("GC start\n")
}
gcphase = _GCmarktermination gcphase = _GCmarktermination
if debug.allocfreetrace > 0 { if debug.allocfreetrace > 0 {
tracegc() tracegc()
} }
_g_ := getg()
_g_.m.traceback = 2 _g_.m.traceback = 2
t0 := start_time t0 := start_time
work.tstart = start_time work.tstart = start_time
...@@ -619,6 +592,7 @@ func gc(start_time int64, eagersweep bool) { ...@@ -619,6 +592,7 @@ func gc(start_time int64, eagersweep bool) {
if debug.gccheckmark > 0 { if debug.gccheckmark > 0 {
if !checkmarkphase { if !checkmarkphase {
// first half of two-pass; don't set up sweep // first half of two-pass; don't set up sweep
casgstatus(gp, _Gwaiting, _Grunning)
return return
} }
checkmarkphase = false // done checking marks checkmarkphase = false // done checking marks
...@@ -666,16 +640,12 @@ func gc(start_time int64, eagersweep bool) { ...@@ -666,16 +640,12 @@ func gc(start_time int64, eagersweep bool) {
if _DebugGCPtrs { if _DebugGCPtrs {
print("GC end\n") print("GC end\n")
} }
casgstatus(gp, _Gwaiting, _Grunning)
} }
// Hooks for other packages // Hooks for other packages
//go:linkname runtime_debug_freeOSMemory runtime/debug.freeOSMemory
func runtime_debug_freeOSMemory() {
gogc(2) // force GC and do eager sweep
systemstack(scavenge_m)
}
var poolcleanup func() var poolcleanup func()
//go:linkname sync_runtime_registerPoolCleanup sync.runtime_registerPoolCleanup //go:linkname sync_runtime_registerPoolCleanup sync.runtime_registerPoolCleanup
......
...@@ -747,8 +747,10 @@ func mHeap_Scavenge(k int32, now, limit uint64) { ...@@ -747,8 +747,10 @@ func mHeap_Scavenge(k int32, now, limit uint64) {
} }
} }
func scavenge_m() { //go:linkname runtime_debug_freeOSMemory runtime/debug.freeOSMemory
mHeap_Scavenge(-1, ^uint64(0), 0) func runtime_debug_freeOSMemory() {
startGC(gcForceBlockMode)
systemstack(func() { mHeap_Scavenge(-1, ^uint64(0), 0) })
} }
// Initialize a new span with the given start and npages. // Initialize a new span with the given start and npages.
......
...@@ -123,7 +123,7 @@ func forcegchelper() { ...@@ -123,7 +123,7 @@ func forcegchelper() {
if debug.gctrace > 0 { if debug.gctrace > 0 {
println("GC forced") println("GC forced")
} }
gogc(1) startGC(gcForceMode)
} }
} }
......
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