Skip to content

Commit 9ac1ee2

Browse files
committed
runtime: track the amount of scannable allocated stack for the GC pacer
This change adds two fields to gcControllerState: stackScan, used for pacing decisions, and scannableStackSize, which directly tracks the amount of space allocated for inuse stacks that will be scanned. scannableStackSize is not updated directly, but is instead flushed from each P when at an least 8 KiB delta has accumulated. This helps reduce issues with atomics contention for newly created goroutines. Stack growth paths are largely unaffected. StackGrowth-48 51.4ns ± 0% 51.4ns ± 0% ~ (p=0.927 n=10+10) StackGrowthDeep-48 6.14µs ± 3% 6.25µs ± 4% ~ (p=0.090 n=10+9) CreateGoroutines-48 273ns ± 1% 273ns ± 1% ~ (p=0.676 n=9+10) CreateGoroutinesParallel-48 65.5ns ± 5% 66.6ns ± 7% ~ (p=0.340 n=9+9) CreateGoroutinesCapture-48 2.06µs ± 1% 2.07µs ± 4% ~ (p=0.217 n=10+10) CreateGoroutinesSingle-48 550ns ± 3% 563ns ± 4% +2.41% (p=0.034 n=8+10) For #44167. Change-Id: Id1800d41d3a6c211b43aeb5681c57c0dc8880daf Reviewed-on: https://go-review.googlesource.com/c/go/+/309589 Trust: Michael Knyszek <[email protected]> Run-TryBot: Michael Knyszek <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
1 parent 8e112a7 commit 9ac1ee2

File tree

4 files changed

+45
-2
lines changed

4 files changed

+45
-2
lines changed

src/runtime/mgcpacer.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ const (
4747

4848
// defaultHeapMinimum is the value of heapMinimum for GOGC==100.
4949
defaultHeapMinimum = 4 << 20
50+
51+
// scannableStackSizeSlack is the bytes of stack space allocated or freed
52+
// that can accumulate on a P before updating gcController.stackSize.
53+
scannableStackSizeSlack = 8 << 10
5054
)
5155

5256
func init() {
@@ -166,6 +170,18 @@ type gcControllerState struct {
166170
// Read and written atomically or with the world stopped.
167171
heapScan uint64
168172

173+
// stackScan is a snapshot of scannableStackSize taken at each GC
174+
// STW pause and is used in pacing decisions.
175+
//
176+
// Updated only while the world is stopped.
177+
stackScan uint64
178+
179+
// scannableStackSize is the amount of allocated goroutine stack space in
180+
// use by goroutines.
181+
//
182+
// Read and updated atomically.
183+
scannableStackSize uint64
184+
169185
// heapMarked is the number of bytes marked by the previous
170186
// GC. After mark termination, heapLive == heapMarked, but
171187
// unlike heapLive, heapMarked does not change until the
@@ -276,6 +292,7 @@ func (c *gcControllerState) startCycle(markStartTime int64) {
276292
c.fractionalMarkTime = 0
277293
c.idleMarkTime = 0
278294
c.markStartTime = markStartTime
295+
c.stackScan = atomic.Load64(&c.scannableStackSize)
279296

280297
// Ensure that the heap goal is at least a little larger than
281298
// the current live heap size. This may not be the case if GC
@@ -686,6 +703,18 @@ func (c *gcControllerState) update(dHeapLive, dHeapScan int64) {
686703
}
687704
}
688705

706+
func (c *gcControllerState) addScannableStack(pp *p, amount int64) {
707+
if pp == nil {
708+
atomic.Xadd64(&c.scannableStackSize, amount)
709+
return
710+
}
711+
pp.scannableStackSizeDelta += amount
712+
if pp.scannableStackSizeDelta >= scannableStackSizeSlack || pp.scannableStackSizeDelta <= -scannableStackSizeSlack {
713+
atomic.Xadd64(&c.scannableStackSize, pp.scannableStackSizeDelta)
714+
pp.scannableStackSizeDelta = 0
715+
}
716+
}
717+
689718
// commit sets the trigger ratio and updates everything
690719
// derived from it: the absolute trigger, the heap goal, mark pacing,
691720
// and sweep pacing.

src/runtime/proc.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3623,8 +3623,10 @@ func goexit1() {
36233623
// goexit continuation on g0.
36243624
func goexit0(gp *g) {
36253625
_g_ := getg()
3626+
_p_ := _g_.m.p.ptr()
36263627

36273628
casgstatus(gp, _Grunning, _Gdead)
3629+
gcController.addScannableStack(_p_, -int64(gp.stack.hi-gp.stack.lo))
36283630
if isSystemGoroutine(gp, false) {
36293631
atomic.Xadd(&sched.ngsys, -1)
36303632
}
@@ -3655,15 +3657,15 @@ func goexit0(gp *g) {
36553657
dropg()
36563658

36573659
if GOARCH == "wasm" { // no threads yet on wasm
3658-
gfput(_g_.m.p.ptr(), gp)
3660+
gfput(_p_, gp)
36593661
schedule() // never returns
36603662
}
36613663

36623664
if _g_.m.lockedInt != 0 {
36633665
print("invalid m->lockedInt = ", _g_.m.lockedInt, "\n")
36643666
throw("internal lockOSThread error")
36653667
}
3666-
gfput(_g_.m.p.ptr(), gp)
3668+
gfput(_p_, gp)
36673669
if locked {
36683670
// The goroutine may have locked this thread because
36693671
// it put it in an unusual kernel state. Kill it
@@ -4292,6 +4294,7 @@ func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
42924294
newg.tracking = true
42934295
}
42944296
casgstatus(newg, _Gdead, _Grunnable)
4297+
gcController.addScannableStack(_p_, int64(newg.stack.hi-newg.stack.lo))
42954298

42964299
if _p_.goidcache == _p_.goidcacheend {
42974300
// Sched.goidgen is the last allocated id,

src/runtime/runtime2.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,12 @@ type p struct {
734734
// Race context used while executing timer functions.
735735
timerRaceCtx uintptr
736736

737+
// scannableStackSizeDelta accumulates the amount of stack space held by
738+
// live goroutines (i.e. those eligible for stack scanning).
739+
// Flushed to gcController.scannableStackSize once scannableStackSizeSlack
740+
// or -scannableStackSizeSlack is reached.
741+
scannableStackSizeDelta int64
742+
737743
// preempt is set to indicate that this P should be enter the
738744
// scheduler ASAP (regardless of what G is running on it).
739745
preempt bool

src/runtime/stack.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,11 @@ func copystack(gp *g, newsize uintptr) {
852852
throw("nil stackbase")
853853
}
854854
used := old.hi - gp.sched.sp
855+
// Add just the difference to gcController.addScannableStack.
856+
// g0 stacks never move, so this will never account for them.
857+
// It's also fine if we have no P, addScannableStack can deal with
858+
// that case.
859+
gcController.addScannableStack(getg().m.p.ptr(), int64(newsize)-int64(old.hi-old.lo))
855860

856861
// allocate new stack
857862
new := stackalloc(uint32(newsize))

0 commit comments

Comments
 (0)