runtime: use inUse ranges to map in summary memory only as needed

Prior to this change, if the heap was very discontiguous (such as in
TestArenaCollision) it's possible we could map a large amount of memory
as R/W and commit it. We would use only the start and end to track what
should be mapped, and we would extend that mapping as needed to
accomodate a potentially fragmented address space.

After this change, we only map exactly the part of the summary arrays
that we need by using the inUse ranges from the previous change. This
reduces the GCSys footprint of TestArenaCollision from 300 MiB to 18
MiB.

Because summaries are no longer mapped contiguously, this means the
scavenger can no longer iterate directly. This change also updates the
scavenger to borrow ranges out of inUse and iterate over only the
parts of the heap which are actually currently in use. This is both an
optimization and necessary for correctness.

Fixes #35514.

Change-Id: I96bf0c73ed0d2d89a00202ece7b9d089a53bac90
Reviewed-on: https://go-review.googlesource.com/c/go/+/207758
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
This commit is contained in:
Michael Anthony Knyszek 2019-11-18 19:23:39 +00:00 committed by Michael Knyszek
parent 6f2b8347b1
commit 1b1fbb3192
6 changed files with 200 additions and 133 deletions

View file

@ -182,6 +182,10 @@ type pageAlloc struct {
// runtime segmentation fault, we get a much friendlier out-of-bounds
// error.
//
// To iterate over a summary level, use inUse to determine which ranges
// are currently available. Otherwise one might try to access
// memory which is only Reserved which may result in a hard fault.
//
// We may still get segmentation faults < len since some of that
// memory may not be committed yet.
summary [summaryLevels][]pallocSum
@ -212,12 +216,9 @@ type pageAlloc struct {
// making the impact on BSS too high (note the L1 is stored directly
// in pageAlloc).
//
// summary[len(s.summary)-1][i] should always be checked, at least
// for a zero max value, before accessing chunks[i]. It's possible the
// bitmap at that index is mapped in and zeroed, indicating that it
// contains free space, but in actuality it is unused since its
// corresponding summary was never updated. Tests may ignore this
// and assume the zero value (and that it is mapped).
// To iterate over the bitmap, use inUse to determine which ranges
// are currently available. Otherwise one might iterate over unused
// ranges.
//
// TODO(mknyszek): Consider changing the definition of the bitmap
// such that 1 means free and 0 means in-use so that summaries and
@ -297,53 +298,6 @@ func (s *pageAlloc) init(mheapLock *mutex, sysStat *uint64) {
s.mheapLock = mheapLock
}
// extendMappedRegion ensures that all the memory in the range
// [base+nbase, base+nlimit) is in the Ready state.
// base must refer to the beginning of a memory region in the
// Reserved state. extendMappedRegion assumes that the region
// [base+mbase, base+mlimit) is already mapped.
//
// Note that extendMappedRegion only supports extending
// mappings in one direction. Therefore,
// nbase < mbase && nlimit > mlimit is an invalid input
// and this function will throw.
func extendMappedRegion(base unsafe.Pointer, mbase, mlimit, nbase, nlimit uintptr, sysStat *uint64) {
if uintptr(base)%physPageSize != 0 {
print("runtime: base = ", base, "\n")
throw("extendMappedRegion: base not page-aligned")
}
// Round the offsets to a physical page.
mbase = alignDown(mbase, physPageSize)
nbase = alignDown(nbase, physPageSize)
mlimit = alignUp(mlimit, physPageSize)
nlimit = alignUp(nlimit, physPageSize)
// If none of the region is mapped, don't bother
// trying to figure out which parts are.
if mlimit-mbase != 0 {
// Determine which part of the region actually needs
// mapping.
if nbase < mbase && nlimit > mlimit {
// TODO(mknyszek): Consider supporting this case. It can't
// ever happen currently in the page allocator, but may be
// useful in the future. Also, it would make this function's
// purpose simpler to explain.
throw("mapped region extended in two directions")
} else if nbase < mbase && nlimit <= mlimit {
nlimit = mbase
} else if nbase >= mbase && nlimit > mlimit {
nbase = mlimit
} else {
return
}
}
// Transition from Reserved to Ready.
rbase := add(base, nbase)
sysMap(rbase, nlimit-nbase, sysStat)
sysUsed(rbase, nlimit-nbase)
}
// compareSearchAddrTo compares an address against s.searchAddr in a linearized
// view of the address space on systems with discontinuous process address spaces.
// This linearized view is the same one generated by chunkIndex and arenaIndex,