Skip to content

Commit 60ab197

Browse files
committed
runtime: refactor work stealing to dedicated function
findrunnable has grown very large and hard to follow over the years. Parts we can split out into logical chunks should help make it more understandable and easier to change in the future. The work stealing loop is one such big chunk that is fairly trivial to split out into its own function, and even has the advantage of simplifying control flow by removing a goto around work stealing. This CL should have no functional changes. For #43997. For #44313. Change-Id: Ie69670c7bc60bd6c114e860184918717829adb22 Reviewed-on: https://go-review.googlesource.com/c/go/+/307913 Trust: Michael Pratt <[email protected]> Run-TryBot: Michael Pratt <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Chris Hines <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent 9dd71ba commit 60ab197

File tree

1 file changed

+105
-75
lines changed

1 file changed

+105
-75
lines changed

src/runtime/proc.go

+105-75
Original file line numberDiff line numberDiff line change
@@ -2681,85 +2681,40 @@ top:
26812681
}
26822682
}
26832683

2684-
// Steal work from other P's.
2684+
// Spinning Ms: steal work from other Ps.
2685+
//
2686+
// Limit the number of spinning Ms to half the number of busy Ps.
2687+
// This is necessary to prevent excessive CPU consumption when
2688+
// GOMAXPROCS>>1 but the program parallelism is low.
26852689
procs := uint32(gomaxprocs)
2686-
ranTimer := false
2687-
// If number of spinning M's >= number of busy P's, block.
2688-
// This is necessary to prevent excessive CPU consumption
2689-
// when GOMAXPROCS>>1 but the program parallelism is low.
2690-
if !_g_.m.spinning && 2*atomic.Load(&sched.nmspinning) >= procs-atomic.Load(&sched.npidle) {
2691-
goto stop
2692-
}
2693-
if !_g_.m.spinning {
2694-
_g_.m.spinning = true
2695-
atomic.Xadd(&sched.nmspinning, 1)
2696-
}
2697-
const stealTries = 4
2698-
for i := 0; i < stealTries; i++ {
2699-
stealTimersOrRunNextG := i == stealTries-1
2700-
2701-
for enum := stealOrder.start(fastrand()); !enum.done(); enum.next() {
2702-
if sched.gcwaiting != 0 {
2703-
goto top
2704-
}
2705-
p2 := allp[enum.position()]
2706-
if _p_ == p2 {
2707-
continue
2708-
}
2709-
2710-
// Steal timers from p2. This call to checkTimers is the only place
2711-
// where we might hold a lock on a different P's timers. We do this
2712-
// once on the last pass before checking runnext because stealing
2713-
// from the other P's runnext should be the last resort, so if there
2714-
// are timers to steal do that first.
2715-
//
2716-
// We only check timers on one of the stealing iterations because
2717-
// the time stored in now doesn't change in this loop and checking
2718-
// the timers for each P more than once with the same value of now
2719-
// is probably a waste of time.
2720-
//
2721-
// timerpMask tells us whether the P may have timers at all. If it
2722-
// can't, no need to check at all.
2723-
if stealTimersOrRunNextG && timerpMask.read(enum.position()) {
2724-
tnow, w, ran := checkTimers(p2, now)
2725-
now = tnow
2726-
if w != 0 && (pollUntil == 0 || w < pollUntil) {
2727-
pollUntil = w
2728-
}
2729-
if ran {
2730-
// Running the timers may have
2731-
// made an arbitrary number of G's
2732-
// ready and added them to this P's
2733-
// local run queue. That invalidates
2734-
// the assumption of runqsteal
2735-
// that is always has room to add
2736-
// stolen G's. So check now if there
2737-
// is a local G to run.
2738-
if gp, inheritTime := runqget(_p_); gp != nil {
2739-
return gp, inheritTime
2740-
}
2741-
ranTimer = true
2742-
}
2743-
}
2690+
if _g_.m.spinning || 2*atomic.Load(&sched.nmspinning) < procs-atomic.Load(&sched.npidle) {
2691+
if !_g_.m.spinning {
2692+
_g_.m.spinning = true
2693+
atomic.Xadd(&sched.nmspinning, 1)
2694+
}
27442695

2745-
// Don't bother to attempt to steal if p2 is idle.
2746-
if !idlepMask.read(enum.position()) {
2747-
if gp := runqsteal(_p_, p2, stealTimersOrRunNextG); gp != nil {
2748-
return gp, false
2749-
}
2750-
}
2696+
gp, inheritTime, tnow, w, newWork := stealWork(now)
2697+
now = tnow
2698+
if gp != nil {
2699+
// Successfully stole.
2700+
return gp, inheritTime
2701+
}
2702+
if newWork {
2703+
// There may be new timer or GC work; restart to
2704+
// discover.
2705+
goto top
2706+
}
2707+
if w != 0 && (pollUntil == 0 || w < pollUntil) {
2708+
// Earlier timer to wait for.
2709+
pollUntil = w
27512710
}
27522711
}
2753-
if ranTimer {
2754-
// Running a timer may have made some goroutine ready.
2755-
goto top
2756-
}
2757-
2758-
stop:
27592712

2760-
// We have nothing to do. If we're in the GC mark phase, can
2761-
// safely scan and blacken objects, and have work to do, run
2762-
// idle-time marking rather than give up the P.
2713+
// We have nothing to do.
2714+
//
2715+
// If we're in the GC mark phase, can safely scan and blacken objects,
2716+
// and have work to do, run idle-time marking rather than give up the
2717+
// P.
27632718
if gcBlackenEnabled != 0 && gcMarkWorkAvailable(_p_) {
27642719
node := (*gcBgMarkWorkerNode)(gcBgMarkWorkerPool.pop())
27652720
if node != nil {
@@ -3008,6 +2963,81 @@ func pollWork() bool {
30082963
return false
30092964
}
30102965

2966+
// stealWork attempts to steal a runnable goroutine or timer from any P.
2967+
//
2968+
// If newWork is true, new work may have been readied.
2969+
//
2970+
// If now is not 0 it is the current time. stealWork returns the passed time or
2971+
// the current time if now was passed as 0.
2972+
func stealWork(now int64) (gp *g, inheritTime bool, rnow, pollUntil int64, newWork bool) {
2973+
pp := getg().m.p.ptr()
2974+
2975+
ranTimer := false
2976+
2977+
const stealTries = 4
2978+
for i := 0; i < stealTries; i++ {
2979+
stealTimersOrRunNextG := i == stealTries-1
2980+
2981+
for enum := stealOrder.start(fastrand()); !enum.done(); enum.next() {
2982+
if sched.gcwaiting != 0 {
2983+
// GC work may be available.
2984+
return nil, false, now, pollUntil, true
2985+
}
2986+
p2 := allp[enum.position()]
2987+
if pp == p2 {
2988+
continue
2989+
}
2990+
2991+
// Steal timers from p2. This call to checkTimers is the only place
2992+
// where we might hold a lock on a different P's timers. We do this
2993+
// once on the last pass before checking runnext because stealing
2994+
// from the other P's runnext should be the last resort, so if there
2995+
// are timers to steal do that first.
2996+
//
2997+
// We only check timers on one of the stealing iterations because
2998+
// the time stored in now doesn't change in this loop and checking
2999+
// the timers for each P more than once with the same value of now
3000+
// is probably a waste of time.
3001+
//
3002+
// timerpMask tells us whether the P may have timers at all. If it
3003+
// can't, no need to check at all.
3004+
if stealTimersOrRunNextG && timerpMask.read(enum.position()) {
3005+
tnow, w, ran := checkTimers(p2, now)
3006+
now = tnow
3007+
if w != 0 && (pollUntil == 0 || w < pollUntil) {
3008+
pollUntil = w
3009+
}
3010+
if ran {
3011+
// Running the timers may have
3012+
// made an arbitrary number of G's
3013+
// ready and added them to this P's
3014+
// local run queue. That invalidates
3015+
// the assumption of runqsteal
3016+
// that it always has room to add
3017+
// stolen G's. So check now if there
3018+
// is a local G to run.
3019+
if gp, inheritTime := runqget(pp); gp != nil {
3020+
return gp, inheritTime, now, pollUntil, ranTimer
3021+
}
3022+
ranTimer = true
3023+
}
3024+
}
3025+
3026+
// Don't bother to attempt to steal if p2 is idle.
3027+
if !idlepMask.read(enum.position()) {
3028+
if gp := runqsteal(pp, p2, stealTimersOrRunNextG); gp != nil {
3029+
return gp, false, now, pollUntil, ranTimer
3030+
}
3031+
}
3032+
}
3033+
}
3034+
3035+
// No goroutines found to steal. Regardless, running a timer may have
3036+
// made some goroutine ready that we missed. Indicate the next timer to
3037+
// wait for.
3038+
return nil, false, now, pollUntil, ranTimer
3039+
}
3040+
30113041
// wakeNetPoller wakes up the thread sleeping in the network poller if it isn't
30123042
// going to wake up before the when argument; or it wakes an idle P to service
30133043
// timers and the network poller if there isn't one already.
@@ -3252,7 +3282,7 @@ func dropg() {
32523282

32533283
// checkTimers runs any timers for the P that are ready.
32543284
// If now is not 0 it is the current time.
3255-
// It returns the current time or 0 if it is not known,
3285+
// It returns the passed time or the current time if now was passed as 0.
32563286
// and the time when the next timer should run or 0 if there is no next timer,
32573287
// and reports whether it ran any timers.
32583288
// If the time when the next timer should run is not 0,

0 commit comments

Comments
 (0)