Skip to content

Commit ce502b0

Browse files
committed
runtime: use park/ready to wake up GC at end of concurrent mark
Currently, the main GC goroutine sleeps on a note during concurrent mark and the first background mark worker or assist to finish marking use wakes up that note to let the main goroutine proceed into mark termination. Unfortunately, the latency of this wakeup can be quite high, since the GC goroutine will typically have lost its P while in the futex sleep, meaning it will be placed on the global run queue and will wait there until some P is kind enough to pick it up. This delay gives the mutator more time to allocate and create floating garbage, growing the heap unnecessarily. Worse, it's likely that background marking has stopped at this point (unless GOMAXPROCS>4), so anything that's allocated and published to the heap during this window will have to be scanned during mark termination while the world is stopped. This change replaces the note sleep/wakeup with a gopark/ready scheme. This keeps the wakeup inside the Go scheduler and lets the garbage collector take advantage of the new scheduler semantics that run the ready()d goroutine immediately when the ready()ing goroutine sleeps. For the json benchmark from x/benchmarks with GOMAXPROCS=4, this reduces the delay in waking up the GC goroutine and entering mark termination once concurrent marking is done from ~100ms to typically <100µs. Change-Id: Ib11f8b581b8914f2d68e0094f121e49bac3bb384 Reviewed-on: https://go-review.googlesource.com/9291 Reviewed-by: Rick Hudson <[email protected]> Reviewed-by: Russ Cox <[email protected]>
1 parent 4e32718 commit ce502b0

File tree

2 files changed

+43
-11
lines changed

2 files changed

+43
-11
lines changed

src/runtime/mgc.go

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,13 @@ var work struct {
590590

591591
bgMarkReady note // signal background mark worker has started
592592
bgMarkDone uint32 // cas to 1 when at a background mark completion point
593-
bgMarkNote note // signal background mark completion
593+
594+
// Background mark completion signaling
595+
bgMarkWake struct {
596+
lock mutex
597+
g *g
598+
wake bool
599+
}
594600

595601
// Copy of mheap.allspans for marker or sweeper.
596602
spans []*mspan
@@ -781,8 +787,18 @@ func gc(mode int) {
781787
if debug.gctrace > 0 {
782788
tMark = nanotime()
783789
}
784-
notetsleepg(&work.bgMarkNote, -1)
785-
noteclear(&work.bgMarkNote)
790+
791+
// Wait for background mark completion.
792+
lock(&work.bgMarkWake.lock)
793+
if work.bgMarkWake.wake {
794+
// Wakeup already happened
795+
unlock(&work.bgMarkWake.lock)
796+
} else {
797+
work.bgMarkWake.g = getg()
798+
goparkunlock(&work.bgMarkWake.lock, "mark wait (idle)", traceEvGoBlock, 1)
799+
}
800+
work.bgMarkWake.wake = false
801+
work.bgMarkWake.g = nil
786802

787803
// Begin mark termination.
788804
gctimer.cycle.markterm = nanotime()
@@ -1054,10 +1070,10 @@ func gcBgMarkWorker(p *p) {
10541070
}
10551071
gcw.dispose()
10561072

1057-
// If this is the first worker to reach a background
1058-
// completion point this cycle, signal the coordinator.
1059-
if done && cas(&work.bgMarkDone, 0, 1) {
1060-
notewakeup(&work.bgMarkNote)
1073+
// If this worker reached a background mark completion
1074+
// point, signal the main GC goroutine.
1075+
if done {
1076+
gcBgMarkDone()
10611077
}
10621078

10631079
duration := nanotime() - startTime
@@ -1073,6 +1089,24 @@ func gcBgMarkWorker(p *p) {
10731089
}
10741090
}
10751091

1092+
// gcBgMarkDone signals the completion of background marking. This can
1093+
// be called multiple times during a cycle; only the first call has
1094+
// any effect.
1095+
func gcBgMarkDone() {
1096+
if cas(&work.bgMarkDone, 0, 1) {
1097+
// This is the first worker to reach completion.
1098+
// Signal the main GC goroutine.
1099+
lock(&work.bgMarkWake.lock)
1100+
if work.bgMarkWake.g == nil {
1101+
// It hasn't parked yet.
1102+
work.bgMarkWake.wake = true
1103+
} else {
1104+
ready(work.bgMarkWake.g, 0)
1105+
}
1106+
unlock(&work.bgMarkWake.lock)
1107+
}
1108+
}
1109+
10761110
// gcMark runs the mark (or, for concurrent GC, mark termination)
10771111
// STW is in effect at this point.
10781112
//TODO go:nowritebarrier

src/runtime/mgcmark.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,8 @@ func gcAssistAlloc(size uintptr, allowAssist bool) {
246246
// signal a completion point.
247247
if xadd(&work.nwait, +1) == work.nproc && work.full == 0 && work.partial == 0 {
248248
// This has reached a background completion
249-
// point. Is it the first this cycle?
250-
if cas(&work.bgMarkDone, 0, 1) {
251-
notewakeup(&work.bgMarkNote)
252-
}
249+
// point.
250+
gcBgMarkDone()
253251
}
254252

255253
duration := nanotime() - startTime

0 commit comments

Comments
 (0)