Skip to content

Commit ff70494

Browse files
committed
runtime: split spans when scavenging if it's more than we need
This change makes it so that during scavenging we split spans when the span we have next for scavenging is larger than the amount of work we have left to do. The purpose of this change is to improve the worst-case behavior of the scavenger: currently, if the scavenger only has a little bit of work to do but sees a very large free span, it will scavenge the whole thing, spending a lot of time to get way ahead of the scavenge pacing for no reason. With this change the scavenger should follow the pacing more closely, but may still over-scavenge by up to a physical huge page since the splitting behavior avoids breaking up huge pages in free spans. This change is also the culmination of the scavenging improvements, so we also include benchmark results for this series (starting from "runtime: merge all treaps into one implementation" until this patch). This patch stack results in average and peak RSS reductions (up to 11% and 7% respectively) for some benchmarks, with mostly minimal performance degredation (3-4% for some benchmarks, ~0% geomean). Each of these benchmarks was executed with GODEBUG=madvdontneed=1 on Linux; the performance degredation is even smaller when MADV_FREE may be used, but the impact on RSS is much harder to measure. Applications that generally maintain a steady heap size for the most part show no change in application performance. These benchmarks are taken from an experimental benchmarking suite representing a variety of open-source Go packages, the raw results may be found here: https://perf.golang.org/search?q=upload:20190509.1 For #30333. Change-Id: I618a48534d2d6ce5f656bb66825e3c383ab1ffba Reviewed-on: https://go-review.googlesource.com/c/go/+/175797 Run-TryBot: Michael Knyszek <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent fe67ea3 commit ff70494

File tree

1 file changed

+62
-1
lines changed

1 file changed

+62
-1
lines changed

src/runtime/mheap.go

+62-1
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,63 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool) {
13501350
h.free.insert(s)
13511351
}
13521352

1353+
// scavengeSplit takes t.span() and attempts to split off a span containing size
1354+
// (in bytes) worth of physical pages from the back.
1355+
//
1356+
// The split point is only approximately defined by size since the split point
1357+
// is aligned to physPageSize and pageSize every time. If physHugePageSize is
1358+
// non-zero and the split point would break apart a huge page in the span, then
1359+
// the split point is also aligned to physHugePageSize.
1360+
//
1361+
// If the desired split point ends up at the base of s, or if size is obviously
1362+
// much larger than s, then a split is not possible and this method returns nil.
1363+
// Otherwise if a split occurred it returns the newly-created span.
1364+
func (h *mheap) scavengeSplit(t treapIter, size uintptr) *mspan {
1365+
s := t.span()
1366+
start, end := s.physPageBounds()
1367+
if end <= start || end-start <= size {
1368+
// Size covers the whole span.
1369+
return nil
1370+
}
1371+
// The span is bigger than what we need, so compute the base for the new
1372+
// span if we decide to split.
1373+
base := end - size
1374+
// Round down to the next physical or logical page, whichever is bigger.
1375+
base &^= (physPageSize - 1) | (pageSize - 1)
1376+
if base <= start {
1377+
return nil
1378+
}
1379+
if physHugePageSize > pageSize && base&^(physHugePageSize-1) >= start {
1380+
// We're in danger of breaking apart a huge page, so include the entire
1381+
// huge page in the bound by rounding down to the huge page size.
1382+
// base should still be aligned to pageSize.
1383+
base &^= physHugePageSize - 1
1384+
}
1385+
if base == start {
1386+
// After all that we rounded base down to s.base(), so no need to split.
1387+
return nil
1388+
}
1389+
if base < start {
1390+
print("runtime: base=", base, ", s.npages=", s.npages, ", s.base()=", s.base(), ", size=", size, "\n")
1391+
print("runtime: physPageSize=", physPageSize, ", physHugePageSize=", physHugePageSize, "\n")
1392+
throw("bad span split base")
1393+
}
1394+
1395+
// Split s in-place, removing from the back.
1396+
n := (*mspan)(h.spanalloc.alloc())
1397+
nbytes := s.base() + s.npages*pageSize - base
1398+
h.free.mutate(t, func(s *mspan) {
1399+
n.init(base, nbytes/pageSize)
1400+
s.npages -= nbytes / pageSize
1401+
h.setSpan(n.base()-1, s)
1402+
h.setSpan(n.base(), n)
1403+
h.setSpan(n.base()+nbytes-1, n)
1404+
n.needzero = s.needzero
1405+
n.state = s.state
1406+
})
1407+
return n
1408+
}
1409+
13531410
// scavengeLocked scavenges nbytes worth of spans in the free treap by
13541411
// starting from the span with the highest base address and working down.
13551412
// It then takes those spans and places them in scav.
@@ -1371,7 +1428,11 @@ func (h *mheap) scavengeLocked(nbytes uintptr) uintptr {
13711428
continue
13721429
}
13731430
n := t.prev()
1374-
h.free.erase(t)
1431+
if span := h.scavengeSplit(t, nbytes-released); span != nil {
1432+
s = span
1433+
} else {
1434+
h.free.erase(t)
1435+
}
13751436
released += s.scavenge()
13761437
// Now that s is scavenged, we must eagerly coalesce it
13771438
// with its neighbors to prevent having two spans with

0 commit comments

Comments
 (0)