Commit 0906d648 authored by Austin Clements's avatar Austin Clements

runtime: eliminate gchelper mechanism

Now that we do no mark work during mark termination, we no longer need
the gchelper mechanism.

Updates #26903.
Updates #17503.

Change-Id: Ie94e5c0f918cfa047e88cae1028fece106955c1b
Reviewed-on: https://go-review.googlesource.com/c/134785
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarRick Hudson <rlh@golang.org>
parent 550dfc8a
...@@ -938,11 +938,10 @@ var work struct { ...@@ -938,11 +938,10 @@ var work struct {
markrootNext uint32 // next markroot job markrootNext uint32 // next markroot job
markrootJobs uint32 // number of markroot jobs markrootJobs uint32 // number of markroot jobs
nproc uint32 nproc uint32
tstart int64 tstart int64
nwait uint32 nwait uint32
ndone uint32 ndone uint32
alldone note
// Number of roots of various root types. Set by gcMarkRootPrepare. // Number of roots of various root types. Set by gcMarkRootPrepare.
nFlushCacheRoots int nFlushCacheRoots int
...@@ -1898,30 +1897,12 @@ func gcMark(start_time int64) { ...@@ -1898,30 +1897,12 @@ func gcMark(start_time int64) {
} }
work.tstart = start_time work.tstart = start_time
work.nwait = 0
work.ndone = 0
work.nproc = uint32(gcprocs())
// Check that there's no marking work remaining. // Check that there's no marking work remaining.
if work.full != 0 || work.markrootNext < work.markrootJobs { if work.full != 0 || work.markrootNext < work.markrootJobs {
print("runtime: full=", hex(work.full), " next=", work.markrootNext, " jobs=", work.markrootJobs, " nDataRoots=", work.nDataRoots, " nBSSRoots=", work.nBSSRoots, " nSpanRoots=", work.nSpanRoots, " nStackRoots=", work.nStackRoots, "\n") print("runtime: full=", hex(work.full), " next=", work.markrootNext, " jobs=", work.markrootJobs, " nDataRoots=", work.nDataRoots, " nBSSRoots=", work.nBSSRoots, " nSpanRoots=", work.nSpanRoots, " nStackRoots=", work.nStackRoots, "\n")
panic("non-empty mark queue after concurrent mark") panic("non-empty mark queue after concurrent mark")
} }
// Clear root marking queue.
work.markrootNext = 0
work.markrootJobs = 0
if work.nproc > 1 {
noteclear(&work.alldone)
helpgc(int32(work.nproc))
}
gchelperstart()
gcw := &getg().m.p.ptr().gcw
gcDrain(gcw, 0)
if debug.gccheckmark > 0 { if debug.gccheckmark > 0 {
// This is expensive when there's a large number of // This is expensive when there's a large number of
// Gs, so only do it if checkmark is also enabled. // Gs, so only do it if checkmark is also enabled.
...@@ -1931,10 +1912,6 @@ func gcMark(start_time int64) { ...@@ -1931,10 +1912,6 @@ func gcMark(start_time int64) {
throw("work.full != 0") throw("work.full != 0")
} }
if work.nproc > 1 {
notesleep(&work.alldone)
}
// Clear out buffers and double-check that all gcWork caches // Clear out buffers and double-check that all gcWork caches
// are empty. This should be ensured by gcMarkDone before we // are empty. This should be ensured by gcMarkDone before we
// enter mark termination. // enter mark termination.
...@@ -2094,43 +2071,6 @@ func clearpools() { ...@@ -2094,43 +2071,6 @@ func clearpools() {
unlock(&sched.deferlock) unlock(&sched.deferlock)
} }
// gchelper runs mark termination tasks on Ps other than the P
// coordinating mark termination.
//
// The caller is responsible for ensuring that this has a P to run on,
// even though it's running during STW. Because of this, it's allowed
// to have write barriers.
//
//go:yeswritebarrierrec
func gchelper() {
_g_ := getg()
_g_.m.traceback = 2
gchelperstart()
// Parallel mark over GC roots and heap
if gcphase == _GCmarktermination {
gcw := &_g_.m.p.ptr().gcw
gcDrain(gcw, 0)
}
nproc := atomic.Load(&work.nproc) // work.nproc can change right after we increment work.ndone
if atomic.Xadd(&work.ndone, +1) == nproc-1 {
notewakeup(&work.alldone)
}
_g_.m.traceback = 0
}
func gchelperstart() {
_g_ := getg()
if _g_.m.helpgc < 0 || _g_.m.helpgc >= _MaxGcproc {
throw("gchelperstart: bad m->helpgc")
}
if _g_ != _g_.m.g0 {
throw("gchelper not running on g0 stack")
}
}
// Timing // Timing
// itoaDiv formats val/(10**dec) into buf. // itoaDiv formats val/(10**dec) into buf.
......
...@@ -685,10 +685,6 @@ func scanstack(gp *g, gcw *gcWork) { ...@@ -685,10 +685,6 @@ func scanstack(gp *g, gcw *gcWork) {
if gp == getg() { if gp == getg() {
throw("can't scan our own stack") throw("can't scan our own stack")
} }
mp := gp.m
if mp != nil && mp.helpgc != 0 {
throw("can't scan gchelper stack")
}
// Shrink the stack if not much of it is being used. // Shrink the stack if not much of it is being used.
shrinkstack(gp) shrinkstack(gp)
......
...@@ -663,59 +663,6 @@ func ready(gp *g, traceskip int, next bool) { ...@@ -663,59 +663,6 @@ func ready(gp *g, traceskip int, next bool) {
} }
} }
func gcprocs() int32 {
// Figure out how many CPUs to use during GC.
// Limited by gomaxprocs, number of actual CPUs, and MaxGcproc.
lock(&sched.lock)
n := gomaxprocs
if n > ncpu {
n = ncpu
}
if n > _MaxGcproc {
n = _MaxGcproc
}
if n > sched.nmidle+1 { // one M is currently running
n = sched.nmidle + 1
}
unlock(&sched.lock)
return n
}
func needaddgcproc() bool {
lock(&sched.lock)
n := gomaxprocs
if n > ncpu {
n = ncpu
}
if n > _MaxGcproc {
n = _MaxGcproc
}
n -= sched.nmidle + 1 // one M is currently running
unlock(&sched.lock)
return n > 0
}
func helpgc(nproc int32) {
_g_ := getg()
lock(&sched.lock)
pos := 0
for n := int32(1); n < nproc; n++ { // one M is currently running
if allp[pos].mcache == _g_.m.mcache {
pos++
}
mp := mget()
if mp == nil {
throw("gcprocs inconsistency")
}
mp.helpgc = n
mp.p.set(allp[pos])
mp.mcache = allp[pos].mcache
pos++
notewakeup(&mp.park)
}
unlock(&sched.lock)
}
// freezeStopWait is a large value that freezetheworld sets // freezeStopWait is a large value that freezetheworld sets
// sched.stopwait to in order to request that all Gs permanently stop. // sched.stopwait to in order to request that all Gs permanently stop.
const freezeStopWait = 0x7fffffff const freezeStopWait = 0x7fffffff
...@@ -1132,11 +1079,6 @@ func stopTheWorldWithSema() { ...@@ -1132,11 +1079,6 @@ func stopTheWorldWithSema() {
} }
} }
func mhelpgc() {
_g_ := getg()
_g_.m.helpgc = -1
}
func startTheWorldWithSema(emitTraceEvent bool) int64 { func startTheWorldWithSema(emitTraceEvent bool) int64 {
_g_ := getg() _g_ := getg()
...@@ -1145,7 +1087,6 @@ func startTheWorldWithSema(emitTraceEvent bool) int64 { ...@@ -1145,7 +1087,6 @@ func startTheWorldWithSema(emitTraceEvent bool) int64 {
list := netpoll(false) // non-blocking list := netpoll(false) // non-blocking
injectglist(&list) injectglist(&list)
} }
add := needaddgcproc()
lock(&sched.lock) lock(&sched.lock)
procs := gomaxprocs procs := gomaxprocs
...@@ -1175,7 +1116,6 @@ func startTheWorldWithSema(emitTraceEvent bool) int64 { ...@@ -1175,7 +1116,6 @@ func startTheWorldWithSema(emitTraceEvent bool) int64 {
} else { } else {
// Start M to run P. Do not start another M below. // Start M to run P. Do not start another M below.
newm(nil, p) newm(nil, p)
add = false
} }
} }
...@@ -1192,16 +1132,6 @@ func startTheWorldWithSema(emitTraceEvent bool) int64 { ...@@ -1192,16 +1132,6 @@ func startTheWorldWithSema(emitTraceEvent bool) int64 {
wakep() wakep()
} }
if add {
// If GC could have used another helper proc, start one now,
// in the hope that it will be available next time.
// It would have been even better to start it before the collection,
// but doing so requires allocating memory, so it's tricky to
// coordinate. This lazy approach works out in practice:
// we don't mind if the first couple gc rounds don't have quite
// the maximum number of procs.
newm(mhelpgc, nil)
}
_g_.m.locks-- _g_.m.locks--
if _g_.m.locks == 0 && _g_.preempt { // restore the preemption request in case we've cleared it in newstack if _g_.m.locks == 0 && _g_.preempt { // restore the preemption request in case we've cleared it in newstack
_g_.stackguard0 = stackPreempt _g_.stackguard0 = stackPreempt
...@@ -1276,10 +1206,7 @@ func mstart1() { ...@@ -1276,10 +1206,7 @@ func mstart1() {
fn() fn()
} }
if _g_.m.helpgc != 0 { if _g_.m != &m0 {
_g_.m.helpgc = 0
stopm()
} else if _g_.m != &m0 {
acquirep(_g_.m.nextp.ptr()) acquirep(_g_.m.nextp.ptr())
_g_.m.nextp = 0 _g_.m.nextp = 0
} }
...@@ -2003,21 +1930,11 @@ func stopm() { ...@@ -2003,21 +1930,11 @@ func stopm() {
throw("stopm spinning") throw("stopm spinning")
} }
retry:
lock(&sched.lock) lock(&sched.lock)
mput(_g_.m) mput(_g_.m)
unlock(&sched.lock) unlock(&sched.lock)
notesleep(&_g_.m.park) notesleep(&_g_.m.park)
noteclear(&_g_.m.park) noteclear(&_g_.m.park)
if _g_.m.helpgc != 0 {
// helpgc() set _g_.m.p and _g_.m.mcache, so we have a P.
gchelper()
// Undo the effects of helpgc().
_g_.m.helpgc = 0
_g_.m.mcache = nil
_g_.m.p = 0
goto retry
}
acquirep(_g_.m.nextp.ptr()) acquirep(_g_.m.nextp.ptr())
_g_.m.nextp = 0 _g_.m.nextp = 0
} }
...@@ -3857,7 +3774,7 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) { ...@@ -3857,7 +3774,7 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
pc = funcPC(_ExternalCode) + sys.PCQuantum pc = funcPC(_ExternalCode) + sys.PCQuantum
} }
stk[0] = pc stk[0] = pc
if mp.preemptoff != "" || mp.helpgc != 0 { if mp.preemptoff != "" {
stk[1] = funcPC(_GC) + sys.PCQuantum stk[1] = funcPC(_GC) + sys.PCQuantum
} else { } else {
stk[1] = funcPC(_System) + sys.PCQuantum stk[1] = funcPC(_System) + sys.PCQuantum
...@@ -4634,7 +4551,7 @@ func schedtrace(detailed bool) { ...@@ -4634,7 +4551,7 @@ func schedtrace(detailed bool) {
if lockedg != nil { if lockedg != nil {
id3 = lockedg.goid id3 = lockedg.goid
} }
print(" M", mp.id, ": p=", id1, " curg=", id2, " mallocing=", mp.mallocing, " throwing=", mp.throwing, " preemptoff=", mp.preemptoff, ""+" locks=", mp.locks, " dying=", mp.dying, " helpgc=", mp.helpgc, " spinning=", mp.spinning, " blocked=", mp.blocked, " lockedg=", id3, "\n") print(" M", mp.id, ": p=", id1, " curg=", id2, " mallocing=", mp.mallocing, " throwing=", mp.throwing, " preemptoff=", mp.preemptoff, ""+" locks=", mp.locks, " dying=", mp.dying, " spinning=", mp.spinning, " blocked=", mp.blocked, " lockedg=", id3, "\n")
} }
lock(&allglock) lock(&allglock)
......
...@@ -424,7 +424,6 @@ type m struct { ...@@ -424,7 +424,6 @@ type m struct {
locks int32 locks int32
dying int32 dying int32
profilehz int32 profilehz int32
helpgc int32
spinning bool // m is out of work and is actively looking for work spinning bool // m is out of work and is actively looking for work
blocked bool // m is blocked on a note blocked bool // m is blocked on a note
inwb bool // m is executing a write barrier inwb bool // m is executing a write barrier
......
...@@ -350,7 +350,7 @@ func stackalloc(n uint32) stack { ...@@ -350,7 +350,7 @@ func stackalloc(n uint32) stack {
} }
var x gclinkptr var x gclinkptr
c := thisg.m.mcache c := thisg.m.mcache
if stackNoCache != 0 || c == nil || thisg.m.preemptoff != "" || thisg.m.helpgc != 0 { if stackNoCache != 0 || c == nil || thisg.m.preemptoff != "" {
// c == nil can happen in the guts of exitsyscall or // c == nil can happen in the guts of exitsyscall or
// procresize. Just get a stack from the global pool. // procresize. Just get a stack from the global pool.
// Also don't touch stackcache during gc // Also don't touch stackcache during gc
...@@ -445,7 +445,7 @@ func stackfree(stk stack) { ...@@ -445,7 +445,7 @@ func stackfree(stk stack) {
} }
x := gclinkptr(v) x := gclinkptr(v)
c := gp.m.mcache c := gp.m.mcache
if stackNoCache != 0 || c == nil || gp.m.preemptoff != "" || gp.m.helpgc != 0 { if stackNoCache != 0 || c == nil || gp.m.preemptoff != "" {
lock(&stackpoolmu) lock(&stackpoolmu)
stackpoolfree(x, order) stackpoolfree(x, order)
unlock(&stackpoolmu) unlock(&stackpoolmu)
......
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