Skip to content

Commit 7407d8e

Browse files
committed
runtime: fix over-aggressive proportional sweep
Currently, sweeping is performed before allocating a span by charging for the entire size of the span requested, rather than the number of bytes actually available for allocation from the returned span. That is, if the returned span is 8K, but already has 6K in use, the mutator is charged for 8K of heap allocation even though it can only allocate 2K more from the span. As a result, proportional sweep is over-aggressive and tends to finish much earlier than it needs to. This effect is more amplified by fragmented heaps. Fix this by reimbursing the mutator for the used space in a span once it has allocated that span. We still have to charge up-front for the worst-case because we don't know which span the mutator will get, but at least we can correct the over-charge once it has a span, which will go toward later span allocations. This has negligible effect on the throughput of the go1 benchmarks and the garbage benchmark. Fixes #12040. Change-Id: I0e23e7a4ccf126cca000fed5067b20017028dd6b Reviewed-on: https://go-review.googlesource.com/16515 Reviewed-by: Rick Hudson <[email protected]>
1 parent bc1f9d2 commit 7407d8e

File tree

2 files changed

+20
-0
lines changed

2 files changed

+20
-0
lines changed

src/runtime/mcentral.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ havespan:
101101
if n == 0 {
102102
throw("empty span")
103103
}
104+
usedBytes := uintptr(s.ref) * s.elemsize
105+
if usedBytes > 0 {
106+
reimburseSweepCredit(usedBytes)
107+
}
104108
if s.freelist.ptr() == nil {
105109
throw("freelist empty")
106110
}

src/runtime/mgcsweep.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,11 @@ func mSpan_Sweep(s *mspan, preserve bool) bool {
354354
// also sweep pages (e.g., for a large allocation), it can pass a
355355
// non-zero callerSweepPages to leave that many pages unswept.
356356
//
357+
// deductSweepCredit makes a worst-case assumption that all spanBytes
358+
// bytes of the ultimately allocated span will be available for object
359+
// allocation. The caller should call reimburseSweepCredit if that
360+
// turns out not to be the case once the span is allocated.
361+
//
357362
// deductSweepCredit is the core of the "proportional sweep" system.
358363
// It uses statistics gathered by the garbage collector to perform
359364
// enough sweeping so that all pages are swept during the concurrent
@@ -379,6 +384,17 @@ func deductSweepCredit(spanBytes uintptr, callerSweepPages uintptr) {
379384
}
380385
}
381386

387+
// reimburseSweepCredit records that unusableBytes bytes of a
388+
// just-allocated span are not available for object allocation. This
389+
// offsets the worst-case charge performed by deductSweepCredit.
390+
func reimburseSweepCredit(unusableBytes uintptr) {
391+
if mheap_.sweepPagesPerByte == 0 {
392+
// Nobody cares about the credit. Avoid the atomic.
393+
return
394+
}
395+
xadd64(&mheap_.spanBytesAlloc, -int64(unusableBytes))
396+
}
397+
382398
func dumpFreeList(s *mspan) {
383399
printlock()
384400
print("runtime: free list of span ", s, ":\n")

0 commit comments

Comments
 (0)