]> Cypherpunks.ru repositories - gostls13.git/blobdiff - src/runtime/mgcmark.go
runtime: make all GC mark workers yield for forEachP
[gostls13.git] / src / runtime / mgcmark.go
index d5c981f17a32683c410321946d95bbbc48156430..004dc88828a0a4efe7fc3b122583631bcad39a73 100644 (file)
@@ -7,7 +7,9 @@
 package runtime
 
 import (
+       "internal/abi"
        "internal/goarch"
+       "internal/goexperiment"
        "runtime/internal/atomic"
        "runtime/internal/sys"
        "unsafe"
@@ -418,7 +420,11 @@ retry:
                // If the CPU limiter is enabled, intentionally don't
                // assist to reduce the amount of CPU time spent in the GC.
                if traced {
-                       traceGCMarkAssistDone()
+                       trace := traceAcquire()
+                       if trace.ok() {
+                               trace.GCMarkAssistDone()
+                               traceRelease(trace)
+                       }
                }
                return
        }
@@ -459,15 +465,22 @@ retry:
                        // We were able to steal all of the credit we
                        // needed.
                        if traced {
-                               traceGCMarkAssistDone()
+                               trace := traceAcquire()
+                               if trace.ok() {
+                                       trace.GCMarkAssistDone()
+                                       traceRelease(trace)
+                               }
                        }
                        return
                }
        }
-
-       if trace.enabled && !traced {
-               traced = true
-               traceGCMarkAssistStart()
+       if traceEnabled() && !traced {
+               trace := traceAcquire()
+               if trace.ok() {
+                       traced = true
+                       trace.GCMarkAssistStart()
+                       traceRelease(trace)
+               }
        }
 
        // Perform assist work
@@ -513,7 +526,11 @@ retry:
                // this G's assist debt, or the GC cycle is over.
        }
        if traced {
-               traceGCMarkAssistDone()
+               trace := traceAcquire()
+               if trace.ok() {
+                       trace.GCMarkAssistDone()
+                       traceRelease(trace)
+               }
        }
 }
 
@@ -536,7 +553,7 @@ func gcAssistAlloc1(gp *g, scanWork int64) {
                // The gcBlackenEnabled check in malloc races with the
                // store that clears it but an atomic check in every malloc
                // would be a performance hit.
-               // Instead we recheck it here on the non-preemptable system
+               // Instead we recheck it here on the non-preemptible system
                // stack to determine if we should perform an assist.
 
                // GC is done, so ignore any remaining debt.
@@ -648,7 +665,7 @@ func gcParkAssist() bool {
                return false
        }
        // Park.
-       goparkunlock(&work.assistQueue.lock, waitReasonGCAssistWait, traceEvGoBlockGC, 2)
+       goparkunlock(&work.assistQueue.lock, waitReasonGCAssistWait, traceBlockGCMarkAssist, 2)
        return true
 }
 
@@ -919,8 +936,8 @@ func scanframeworker(frame *stkframe, state *stackScanState, gcw *gcWork) {
                print("scanframe ", funcname(frame.fn), "\n")
        }
 
-       isAsyncPreempt := frame.fn.valid() && frame.fn.funcID == funcID_asyncPreempt
-       isDebugCall := frame.fn.valid() && frame.fn.funcID == funcID_debugCallV2
+       isAsyncPreempt := frame.fn.valid() && frame.fn.funcID == abi.FuncID_asyncPreempt
+       isDebugCall := frame.fn.valid() && frame.fn.funcID == abi.FuncID_debugCallV2
        if state.conservative || isAsyncPreempt || isDebugCall {
                if debugScanConservative {
                        println("conservatively scanning function", funcname(frame.fn), "at PC", hex(frame.continpc))
@@ -963,7 +980,7 @@ func scanframeworker(frame *stkframe, state *stackScanState, gcw *gcWork) {
                return
        }
 
-       locals, args, objs := frame.getStackMap(&state.cache, false)
+       locals, args, objs := frame.getStackMap(false)
 
        // Scan local variables if stack frame has been allocated.
        if locals.n > 0 {
@@ -1010,6 +1027,28 @@ const (
        gcDrainFractional
 )
 
+// gcDrainMarkWorkerIdle is a wrapper for gcDrain that exists to better account
+// mark time in profiles.
+func gcDrainMarkWorkerIdle(gcw *gcWork) {
+       gcDrain(gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)
+}
+
+// gcDrainMarkWorkerDedicated is a wrapper for gcDrain that exists to better account
+// mark time in profiles.
+func gcDrainMarkWorkerDedicated(gcw *gcWork, untilPreempt bool) {
+       flags := gcDrainFlushBgCredit
+       if untilPreempt {
+               flags |= gcDrainUntilPreempt
+       }
+       gcDrain(gcw, flags)
+}
+
+// gcDrainMarkWorkerFractional is a wrapper for gcDrain that exists to better account
+// mark time in profiles.
+func gcDrainMarkWorkerFractional(gcw *gcWork) {
+       gcDrain(gcw, gcDrainFractional|gcDrainUntilPreempt|gcDrainFlushBgCredit)
+}
+
 // gcDrain scans roots and objects in work buffers, blackening grey
 // objects until it is unable to get more work. It may return before
 // GC is done; it's the caller's responsibility to balance work from
@@ -1029,15 +1068,26 @@ const (
 // credit to gcController.bgScanCredit every gcCreditSlack units of
 // scan work.
 //
-// gcDrain will always return if there is a pending STW.
+// gcDrain will always return if there is a pending STW or forEachP.
+//
+// Disabling write barriers is necessary to ensure that after we've
+// confirmed that we've drained gcw, that we don't accidentally end
+// up flipping that condition by immediately adding work in the form
+// of a write barrier buffer flush.
+//
+// Don't set nowritebarrierrec because it's safe for some callees to
+// have write barriers enabled.
 //
 //go:nowritebarrier
 func gcDrain(gcw *gcWork, flags gcDrainFlags) {
-       if !writeBarrier.needed {
+       if !writeBarrier.enabled {
                throw("gcDrain phase incorrect")
        }
 
+       // N.B. We must be running in a non-preemptible context, so it's
+       // safe to hold a reference to our P here.
        gp := getg().m.curg
+       pp := gp.m.p.ptr()
        preemptible := flags&gcDrainUntilPreempt != 0
        flushBgCredit := flags&gcDrainFlushBgCredit != 0
        idle := flags&gcDrainIdle != 0
@@ -1059,8 +1109,9 @@ func gcDrain(gcw *gcWork, flags gcDrainFlags) {
 
        // Drain root marking jobs.
        if work.markrootNext < work.markrootJobs {
-               // Stop if we're preemptible or if someone wants to STW.
-               for !(gp.preempt && (preemptible || sched.gcwaiting.Load())) {
+               // Stop if we're preemptible, if someone wants to STW, or if
+               // someone is calling forEachP.
+               for !(gp.preempt && (preemptible || sched.gcwaiting.Load() || pp.runSafePointFn != 0)) {
                        job := atomic.Xadd(&work.markrootNext, +1) - 1
                        if job >= work.markrootJobs {
                                break
@@ -1073,8 +1124,16 @@ func gcDrain(gcw *gcWork, flags gcDrainFlags) {
        }
 
        // Drain heap marking jobs.
-       // Stop if we're preemptible or if someone wants to STW.
-       for !(gp.preempt && (preemptible || sched.gcwaiting.Load())) {
+       //
+       // Stop if we're preemptible, if someone wants to STW, or if
+       // someone is calling forEachP.
+       //
+       // TODO(mknyszek): Consider always checking gp.preempt instead
+       // of having the preempt flag, and making an exception for certain
+       // mark workers in retake. That might be simpler than trying to
+       // enumerate all the reasons why we might want to preempt, even
+       // if we're supposed to be mostly non-preemptible.
+       for !(gp.preempt && (preemptible || sched.gcwaiting.Load() || pp.runSafePointFn != 0)) {
                // Try to keep work available on the global queue. We used to
                // check if there were waiting workers, but it's better to
                // just keep work available than to make workers wait. In the
@@ -1147,7 +1206,7 @@ done:
 //go:nowritebarrier
 //go:systemstack
 func gcDrainN(gcw *gcWork, scanWork int64) int64 {
-       if !writeBarrier.needed {
+       if !writeBarrier.enabled {
                throw("gcDrainN phase incorrect")
        }
 
@@ -1275,6 +1334,7 @@ func scanobject(b uintptr, gcw *gcWork) {
                throw("scanobject of a noscan object")
        }
 
+       var tp typePointers
        if n > maxObletBytes {
                // Large object. Break into oblets for better
                // parallelism and lower latency.
@@ -1295,18 +1355,35 @@ func scanobject(b uintptr, gcw *gcWork) {
                // must be a large object, s.base() is the beginning
                // of the object.
                n = s.base() + s.elemsize - b
-               if n > maxObletBytes {
-                       n = maxObletBytes
+               n = min(n, maxObletBytes)
+               if goexperiment.AllocHeaders {
+                       tp = s.typePointersOfUnchecked(s.base())
+                       tp = tp.fastForward(b-tp.addr, b+n)
+               }
+       } else {
+               if goexperiment.AllocHeaders {
+                       tp = s.typePointersOfUnchecked(b)
                }
        }
 
-       hbits := heapBitsForAddr(b, n)
+       var hbits heapBits
+       if !goexperiment.AllocHeaders {
+               hbits = heapBitsForAddr(b, n)
+       }
        var scanSize uintptr
        for {
                var addr uintptr
-               if hbits, addr = hbits.nextFast(); addr == 0 {
-                       if hbits, addr = hbits.next(); addr == 0 {
-                               break
+               if goexperiment.AllocHeaders {
+                       if tp, addr = tp.nextFast(); addr == 0 {
+                               if tp, addr = tp.next(b + n); addr == 0 {
+                                       break
+                               }
+                       }
+               } else {
+                       if hbits, addr = hbits.nextFast(); addr == 0 {
+                               if hbits, addr = hbits.next(); addr == 0 {
+                                       break
+                               }
                        }
                }