Skip to content

Commit 2491c5f

Browse files
committed
runtime: wake scavenger and update address on sweep done
This change modifies the semantics of waking the scavenger: rather than wake on any update to pacing, wake when we know we will have work to do, that is, when the sweeper is done. The current scavenger runs over the address space just once per GC cycle, and we want to maximize the chance that the scavenger observes the most attractive scavengable memory in that pass (i.e. free memory with the highest address), so the timing is important. By having the scavenger awaken and reset its search space when the sweeper is done, we increase the chance that the scavenger will observe the most attractive scavengable memory, because no more memory will be freed that GC cycle (so the highest scavengable address should now be available). Furthermore, in applications that go idle, this means the background scavenger will be awoken even if another GC doesn't happen, which isn't true today. However, we're unable to wake the scavenger directly from within the sweeper; waking the scavenger involves modifying timers and readying goroutines, the latter of which may trigger an allocation today (and the sweeper may run during allocation!). Instead, we do the following: 1. Set a flag which is checked by sysmon. sysmon will clear the flag and wake the scavenger. 2. Wake the scavenger unconditionally at sweep termination. The idea behind this policy is that it gets us close enough to the state above without having to deal with the complexity of waking the scavenger in deep parts of the runtime. If the application goes idle and sweeping finishes (so we don't reach sweep termination), then sysmon will wake the scavenger. sysmon has a worst-case 20 ms delay in responding to this signal, which is probably fine if the application is completely idle anyway, but if the application is actively allocating, then the proportional sweeper should help ensure that sweeping ends very close to sweep termination, so sweep termination is a perfectly reasonable time to wake up the scavenger. Updates #35788. Change-Id: I84289b37816a7d595d803c72a71b7f5c59d47e6b Reviewed-on: https://go-review.googlesource.com/c/go/+/207998 Run-TryBot: Michael Knyszek <mknyszek@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Austin Clements <austin@google.com>
1 parent c791537 commit 2491c5f

File tree

4 files changed

+64
-18
lines changed

4 files changed

+64
-18
lines changed

src/runtime/mgc.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,6 @@ func setGCPercent(in int32) (out int32) {
236236
gcSetTriggerRatio(memstats.triggerRatio)
237237
unlock(&mheap_.lock)
238238
})
239-
// Pacing changed, so the scavenger should be awoken.
240-
wakeScavenger()
241239

242240
// If we just disabled GC, wait for any concurrent GC mark to
243241
// finish so we always return with no GC running.
@@ -1707,9 +1705,6 @@ func gcMarkTermination(nextTriggerRatio float64) {
17071705
// Update GC trigger and pacing for the next cycle.
17081706
gcSetTriggerRatio(nextTriggerRatio)
17091707

1710-
// Pacing changed, so the scavenger should be awoken.
1711-
wakeScavenger()
1712-
17131708
// Update timing memstats
17141709
now := nanotime()
17151710
sec, nsec, _ := time_now()

src/runtime/mgcscavenge.go

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -150,24 +150,39 @@ func gcPaceScavenger() {
150150
return
151151
}
152152
mheap_.scavengeGoal = retainedGoal
153-
mheap_.pages.resetScavengeAddr()
154153
}
155154

156155
// Sleep/wait state of the background scavenger.
157156
var scavenge struct {
158-
lock mutex
159-
g *g
160-
parked bool
161-
timer *timer
157+
lock mutex
158+
g *g
159+
parked bool
160+
timer *timer
161+
sysmonWake uint32 // Set atomically.
162162
}
163163

164-
// wakeScavenger unparks the scavenger if necessary. It must be called
165-
// after any pacing update.
164+
// readyForScavenger signals sysmon to wake the scavenger because
165+
// there may be new work to do.
166166
//
167-
// mheap_.lock and scavenge.lock must not be held.
167+
// There may be a significant delay between when this function runs
168+
// and when the scavenger is kicked awake, but it may be safely invoked
169+
// in contexts where wakeScavenger is unsafe to call directly.
170+
func readyForScavenger() {
171+
atomic.Store(&scavenge.sysmonWake, 1)
172+
}
173+
174+
// wakeScavenger immediately unparks the scavenger if necessary.
175+
//
176+
// May run without a P, but it may allocate, so it must not be called
177+
// on any allocation path.
178+
//
179+
// mheap_.lock, scavenge.lock, and sched.lock must not be held.
168180
func wakeScavenger() {
169181
lock(&scavenge.lock)
170182
if scavenge.parked {
183+
// Notify sysmon that it shouldn't bother waking up the scavenger.
184+
atomic.Store(&scavenge.sysmonWake, 0)
185+
171186
// Try to stop the timer but we don't really care if we succeed.
172187
// It's possible that either a timer was never started, or that
173188
// we're racing with it.
@@ -183,9 +198,16 @@ func wakeScavenger() {
183198
// scavenger at a "lower priority" but that's OK because it'll
184199
// catch up on the work it missed when it does get scheduled.
185200
scavenge.parked = false
186-
systemstack(func() {
187-
ready(scavenge.g, 0, false)
188-
})
201+
202+
// Ready the goroutine by injecting it. We use injectglist instead
203+
// of ready or goready in order to allow us to run this function
204+
// without a P. injectglist also avoids placing the goroutine in
205+
// the current P's runnext slot, which is desireable to prevent
206+
// the scavenger from interfering with user goroutine scheduling
207+
// too much.
208+
var list gList
209+
list.push(scavenge.g)
210+
injectglist(&list)
189211
}
190212
unlock(&scavenge.lock)
191213
}
@@ -402,8 +424,7 @@ func printScavTrace(released uintptr, forced bool) {
402424
}
403425

404426
// resetScavengeAddr sets the scavenge start address to the top of the heap's
405-
// address space. This should be called each time the scavenger's pacing
406-
// changes.
427+
// address space. This should be called whenever the sweeper is done.
407428
//
408429
// s.mheapLock must be held.
409430
func (s *pageAlloc) resetScavengeAddr() {

src/runtime/mgcsweep.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ func finishsweep_m() {
145145
}
146146
}
147147

148+
// Sweeping is done, so if the scavenger isn't already awake,
149+
// wake it up. There's definitely work for it to do at this
150+
// point.
151+
wakeScavenger()
152+
148153
nextMarkBitArenaEpoch()
149154
}
150155

@@ -241,6 +246,27 @@ func sweepone() uintptr {
241246
// Decrement the number of active sweepers and if this is the
242247
// last one print trace information.
243248
if atomic.Xadd(&mheap_.sweepers, -1) == 0 && atomic.Load(&mheap_.sweepdone) != 0 {
249+
// Since the sweeper is done, reset the scavenger's pointer
250+
// into the heap and wake it if necessary.
251+
//
252+
// The scavenger is signaled by the last sweeper because once
253+
// sweeping is done, we will definitely have useful work for
254+
// the scavenger to do, since the scavenger only runs over the
255+
// heap once per GC cyle. This update is not done during sweep
256+
// termination because in some cases there may be a long delay
257+
// between sweep done and sweep termination (e.g. not enough
258+
// allocations to trigger a GC) which would be nice to fill in
259+
// with scavenging work.
260+
systemstack(func() {
261+
lock(&mheap_.lock)
262+
mheap_.pages.resetScavengeAddr()
263+
unlock(&mheap_.lock)
264+
})
265+
// Since we might sweep in an allocation path, it's not possible
266+
// for us to wake the scavenger directly via wakeScavenger, since
267+
// it could allocate. Ask sysmon to do it for us instead.
268+
readyForScavenger()
269+
244270
if debug.gcpacertrace > 0 {
245271
print("pacer: sweep done at heap size ", memstats.heap_live>>20, "MB; allocated ", (memstats.heap_live-mheap_.sweepHeapLiveBasis)>>20, "MB during sweep; swept ", mheap_.pagesSwept, " pages at ", sweepRatio, " pages/byte\n")
246272
}

src/runtime/proc.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4633,6 +4633,10 @@ func sysmon() {
46334633
// Try to start an M to run them.
46344634
startm(nil, false)
46354635
}
4636+
if atomic.Load(&scavenge.sysmonWake) != 0 {
4637+
// Kick the scavenger awake if someone requested it.
4638+
wakeScavenger()
4639+
}
46364640
// retake P's blocked in syscalls
46374641
// and preempt long running G's
46384642
if retake(now) != 0 {

0 commit comments

Comments
 (0)