Skip to content

Instantly share code, notes, and snippets.

@lysu
Last active December 27, 2018 08:09
Show Gist options
  • Save lysu/51b7d83753be97690f846110c7c033f3 to your computer and use it in GitHub Desktop.
Save lysu/51b7d83753be97690f846110c7c033f3 to your computer and use it in GitHub Desktop.
findrunnable
ROUTINE ======================== runtime.findrunnable in /usr/local/go/src/runtime/proc.go
4.80s 4.59mins (flat, cum) 41.09% of Total
. . 2270: // The conditions here and in handoffp must agree: if
. . 2271: // findrunnable would return a G to run, handoffp must start
. . 2272: // an M.
. . 2273:
. . 2274:top:
220ms 220ms 2275: _p_ := _g_.m.p.ptr()
50ms 50ms 2276: if sched.gcwaiting != 0 {
. 7.04s 2277: gcstopm()
. . 2278: goto top
. . 2279: }
10ms 10ms 2280: if _p_.runSafePointFn != 0 {
. 930ms 2281: runSafePointFn()
. . 2282: }
. . 2283: if fingwait && fingwake {
. . 2284: if gp := wakefing(); gp != nil {
. . 2285: ready(gp, 0, true)
. . 2286: }
. . 2287: }
10ms 10ms 2288: if *cgo_yield != nil {
. . 2289: asmcgocall(*cgo_yield, nil)
. . 2290: }
. . 2291:
. . 2292: // local runq
. 30ms 2293: if gp, inheritTime := runqget(_p_); gp != nil {
. . 2294: return gp, inheritTime
. . 2295: }
. . 2296:
. . 2297: // global runq
410ms 410ms 2298: if sched.runqsize != 0 {
20ms 1.20mins 2299: lock(&sched.lock)
10ms 450ms 2300: gp := globrunqget(_p_, 0)
. 53.73s 2301: unlock(&sched.lock)
. . 2302: if gp != nil {
. . 2303: return gp, false
. . 2304: }
. . 2305: }
. . 2306:
. . 2307: // Poll network.
. . 2308: // This netpoll is only an optimization before we resort to stealing.
. . 2309: // We can safely skip it if there are no waiters or a thread is blocked
. . 2310: // in netpoll already. If there is any kind of logical race with that
. . 2311: // blocked thread (e.g. it has already returned from netpoll, but does
. . 2312: // not set lastpoll yet), this thread will do blocking netpoll below
. . 2313: // anyway.
340ms 360ms 2314: if netpollinited() && atomic.Load(&netpollWaiters) > 0 && atomic.Load64(&sched.lastpoll) != 0 {
30ms 21.35s 2315: if gp := netpoll(false); gp != nil { // non-blocking
. . 2316: // netpoll returns list of goroutines linked by schedlink.
. 8.83s 2317: injectglist(gp.schedlink.ptr())
. 20ms 2318: casgstatus(gp, _Gwaiting, _Grunnable)
10ms 10ms 2319: if trace.enabled {
. . 2320: traceGoUnpark(gp, 0)
. . 2321: }
. . 2322: return gp, false
. . 2323: }
. . 2324: }
. . 2325:
. . 2326: // Steal work from other P's.
. . 2327: procs := uint32(gomaxprocs)
100ms 100ms 2328: if atomic.Load(&sched.npidle) == procs-1 {
. . 2329: // Either GOMAXPROCS=1 or everybody, except for us, is idle already.
. . 2330: // New work can appear from returning syscall/cgocall, network or timers.
. . 2331: // Neither of that submits to local run queues, so no point in stealing.
. . 2332: goto stop
. . 2333: }
. . 2334: // If number of spinning M's >= number of busy P's, block.
. . 2335: // This is necessary to prevent excessive CPU consumption
. . 2336: // when GOMAXPROCS>>1 but the program parallelism is low.
20ms 20ms 2337: if !_g_.m.spinning && 2*atomic.Load(&sched.nmspinning) >= procs-atomic.Load(&sched.npidle) {
. . 2338: goto stop
. . 2339: }
. . 2340: if !_g_.m.spinning {
. . 2341: _g_.m.spinning = true
180ms 180ms 2342: atomic.Xadd(&sched.nmspinning, 1)
. . 2343: }
. . 2344: for i := 0; i < 4; i++ {
510ms 3.75s 2345: for enum := stealOrder.start(fastrand()); !enum.done(); enum.next() {
620ms 620ms 2346: if sched.gcwaiting != 0 {
. . 2347: goto top
. . 2348: }
100ms 100ms 2349: stealRunNextG := i > 2 // first look for ready queues with more than 1 g
1.71s 27.55s 2350: if gp := runqsteal(_p_, allp[enum.position()], stealRunNextG); gp != nil {
10ms 10ms 2351: return gp, false
. . 2352: }
. . 2353: }
. . 2354: }
. . 2355:
. . 2356:stop:
. . 2357:
. . 2358: // We have nothing to do. If we're in the GC mark phase, can
. . 2359: // safely scan and blacken objects, and have work to do, run
. . 2360: // idle-time marking rather than give up the P.
100ms 290ms 2361: if gcBlackenEnabled != 0 && _p_.gcBgMarkWorker != 0 && gcMarkWorkAvailable(_p_) {
. . 2362: _p_.gcMarkWorkerMode = gcMarkWorkerIdleMode
. 10ms 2363: gp := _p_.gcBgMarkWorker.ptr()
. 90ms 2364: casgstatus(gp, _Gwaiting, _Grunnable)
. . 2365: if trace.enabled {
. . 2366: traceGoUnpark(gp, 0)
. . 2367: }
. . 2368: return gp, false
. . 2369: }
. . 2370:
. . 2371: // wasm only:
. . 2372: // Check if a goroutine is waiting for a callback from the WebAssembly host.
. . 2373: // If yes, pause the execution until a callback was triggered.
. . 2374: if pauseSchedulerUntilCallback() {
. . 2375: // A callback was triggered and caused at least one goroutine to wake up.
. . 2376: goto top
. . 2377: }
. . 2378:
. . 2379: // Before we drop our P, make a snapshot of the allp slice,
. . 2380: // which can change underfoot once we no longer block
. . 2381: // safe-points. We don't need to snapshot the contents because
. . 2382: // everything up to cap(allp) is immutable.
. . 2383: allpSnapshot := allp
. . 2384:
. . 2385: // return P and block
. 34.53s 2386: lock(&sched.lock)
20ms 20ms 2387: if sched.gcwaiting != 0 || _p_.runSafePointFn != 0 {
. 930ms 2388: unlock(&sched.lock)
. . 2389: goto top
. . 2390: }
120ms 120ms 2391: if sched.runqsize != 0 {
10ms 50ms 2392: gp := globrunqget(_p_, 0)
. 14.19s 2393: unlock(&sched.lock)
10ms 10ms 2394: return gp, false
. . 2395: }
. 10ms 2396: if releasep() != _p_ {
. . 2397: throw("findrunnable: wrong p")
. . 2398: }
. 230ms 2399: pidleput(_p_)
. 10.47s 2400: unlock(&sched.lock)
. . 2401:
. . 2402: // Delicate dance: thread transitions from spinning to non-spinning state,
. . 2403: // potentially concurrently with submission of new goroutines. We must
. . 2404: // drop nmspinning first and then check all per-P queues again (with
. . 2405: // #StoreLoad memory barrier in between). If we do it the other way around,
. . 2406: // another thread can submit a goroutine after we've checked all run queues
. . 2407: // but before we drop nmspinning; as the result nobody will unpark a thread
. . 2408: // to run the goroutine.
. . 2409: // If we discover new work below, we need to restore m.spinning as a signal
. . 2410: // for resetspinning to unpark a new worker thread (because there can be more
. . 2411: // than one starving goroutine). However, if after discovering new work
. . 2412: // we also observe no idle Ps, it is OK to just park the current thread:
. . 2413: // the system is fully loaded so no spinning threads are required.
. . 2414: // Also see "Worker thread parking/unparking" comment at the top of the file.
20ms 20ms 2415: wasSpinning := _g_.m.spinning
. . 2416: if _g_.m.spinning {
. . 2417: _g_.m.spinning = false
40ms 40ms 2418: if int32(atomic.Xadd(&sched.nmspinning, -1)) < 0 {
. . 2419: throw("findrunnable: negative nmspinning")
. . 2420: }
. . 2421: }
. . 2422:
. . 2423: // check all runqueues once again
40ms 40ms 2424: for _, _p_ := range allpSnapshot {
30ms 310ms 2425: if !runqempty(_p_) {
. 4.89s 2426: lock(&sched.lock)
. 20ms 2427: _p_ = pidleget()
30ms 3.62s 2428: unlock(&sched.lock)
. . 2429: if _p_ != nil {
. 10ms 2430: acquirep(_p_)
. . 2431: if wasSpinning {
. . 2432: _g_.m.spinning = true
. . 2433: atomic.Xadd(&sched.nmspinning, 1)
. . 2434: }
. . 2435: goto top
. . 2436: }
. . 2437: break
. . 2438: }
. . 2439: }
. . 2440:
. . 2441: // Check for idle-priority GC work again.
. . 2442: if gcBlackenEnabled != 0 && gcMarkWorkAvailable(nil) {
. 20ms 2443: lock(&sched.lock)
. . 2444: _p_ = pidleget()
. . 2445: if _p_ != nil && _p_.gcBgMarkWorker == 0 {
. . 2446: pidleput(_p_)
. . 2447: _p_ = nil
. . 2448: }
. . 2449: unlock(&sched.lock)
. . 2450: if _p_ != nil {
. . 2451: acquirep(_p_)
. . 2452: if wasSpinning {
. . 2453: _g_.m.spinning = true
. . 2454: atomic.Xadd(&sched.nmspinning, 1)
. . 2455: }
. . 2456: // Go back to idle GC check.
. . 2457: goto stop
. . 2458: }
. . 2459: }
. . 2460:
. . 2461: // poll network
10ms 10ms 2462: if netpollinited() && atomic.Load(&netpollWaiters) > 0 && atomic.Xchg64(&sched.lastpoll, 0) != 0 {
. . 2463: if _g_.m.p != 0 {
. . 2464: throw("findrunnable: netpoll with p")
. . 2465: }
. . 2466: if _g_.m.spinning {
. . 2467: throw("findrunnable: netpoll with spinning")
. . 2468: }
. 660ms 2469: gp := netpoll(true) // block until new work is available
10ms 10ms 2470: atomic.Store64(&sched.lastpoll, uint64(nanotime()))
. . 2471: if gp != nil {
. 810ms 2472: lock(&sched.lock)
. . 2473: _p_ = pidleget()
. 810ms 2474: unlock(&sched.lock)
. . 2475: if _p_ != nil {
. . 2476: acquirep(_p_)
. 430ms 2477: injectglist(gp.schedlink.ptr())
. . 2478: casgstatus(gp, _Gwaiting, _Grunnable)
. . 2479: if trace.enabled {
. . 2480: traceGoUnpark(gp, 0)
. . 2481: }
. . 2482: return gp, false
. . 2483: }
. 400ms 2484: injectglist(gp)
. . 2485: }
. . 2486: }
. 4.63s 2487: stopm()
. . 2488: goto top
. . 2489:}
. . 2490:
. . 2491:// pollWork returns true if there is non-background work this P could
. . 2492:// be doing. This is a fairly lightweight check to be used for
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment