aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/runtime/mheap.go
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2019-01-18 19:04:36 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2019-01-18 19:04:36 +0000
commit4f4a855d82a889cebcfca150a7a43909bcb6a346 (patch)
treef12bae0781920fa34669fe30b6f4615a86d9fb80 /libgo/go/runtime/mheap.go
parent225220d668dafb8262db7012bced688acbe63b33 (diff)
downloadgcc-4f4a855d82a889cebcfca150a7a43909bcb6a346.zip
gcc-4f4a855d82a889cebcfca150a7a43909bcb6a346.tar.gz
gcc-4f4a855d82a889cebcfca150a7a43909bcb6a346.tar.bz2
libgo: update to Go1.12beta2
Reviewed-on: https://go-review.googlesource.com/c/158019 gotools/: * Makefile.am (go_cmd_vet_files): Update for Go1.12beta2 release. (GOTOOLS_TEST_TIMEOUT): Increase to 600. (check-runtime): Export LD_LIBRARY_PATH before computing GOARCH and GOOS. (check-vet): Copy golang.org/x/tools into check-vet-dir. * Makefile.in: Regenerate. gcc/testsuite/: * go.go-torture/execute/names-1.go: Stop using debug/xcoff, which is no longer externally visible. From-SVN: r268084
Diffstat (limited to 'libgo/go/runtime/mheap.go')
-rw-r--r--libgo/go/runtime/mheap.go788
1 files changed, 485 insertions, 303 deletions
diff --git a/libgo/go/runtime/mheap.go b/libgo/go/runtime/mheap.go
index eb98083..7139b0e 100644
--- a/libgo/go/runtime/mheap.go
+++ b/libgo/go/runtime/mheap.go
@@ -9,6 +9,7 @@
package runtime
import (
+ "internal/cpu"
"runtime/internal/atomic"
"runtime/internal/sys"
"unsafe"
@@ -20,7 +21,7 @@ import (
const minPhysPageSize = 4096
// Main malloc heap.
-// The heap itself is the "free[]" and "large" arrays,
+// The heap itself is the "free" and "scav" treaps,
// but all the other global data is here too.
//
// mheap must not be heap-allocated because it contains mSpanLists,
@@ -29,13 +30,11 @@ const minPhysPageSize = 4096
//go:notinheap
type mheap struct {
lock mutex
- free [_MaxMHeapList]mSpanList // free lists of given length up to _MaxMHeapList
- freelarge mTreap // free treap of length >= _MaxMHeapList
- busy [_MaxMHeapList]mSpanList // busy lists of large spans of given length
- busylarge mSpanList // busy lists of large spans length >= _MaxMHeapList
- sweepgen uint32 // sweep generation, see comment in mspan
- sweepdone uint32 // all spans are swept
- sweepers uint32 // number of active sweepone calls
+ free mTreap // free and non-scavenged spans
+ scav mTreap // free and scavenged spans
+ sweepgen uint32 // sweep generation, see comment in mspan
+ sweepdone uint32 // all spans are swept
+ sweepers uint32 // number of active sweepone calls
// allspans is a slice of all mspans ever created. Each mspan
// appears exactly once.
@@ -61,7 +60,7 @@ type mheap struct {
// on the swept stack.
sweepSpans [2]gcSweepBuf
- //_ uint32 // align uint64 fields on 32-bit for atomics
+ _ uint32 // align uint64 fields on 32-bit for atomics
// Proportional sweep
//
@@ -81,7 +80,7 @@ type mheap struct {
// accounting for current progress. If we could only adjust
// the slope, it would create a discontinuity in debt if any
// progress has already been made.
- pagesInUse uint64 // pages of spans in stats _MSpanInUse; R/W with mheap.lock
+ pagesInUse uint64 // pages of spans in stats mSpanInUse; R/W with mheap.lock
pagesSwept uint64 // pages swept this cycle; updated atomically
pagesSweptBasis uint64 // pagesSwept to use as the origin of the sweep ratio; updated atomically
sweepHeapLiveBasis uint64 // value of heap_live to use as the origin of sweep ratio; written with lock, read without
@@ -89,6 +88,25 @@ type mheap struct {
// TODO(austin): pagesInUse should be a uintptr, but the 386
// compiler can't 8-byte align fields.
+ // Page reclaimer state
+
+ // reclaimIndex is the page index in allArenas of next page to
+ // reclaim. Specifically, it refers to page (i %
+ // pagesPerArena) of arena allArenas[i / pagesPerArena].
+ //
+ // If this is >= 1<<63, the page reclaimer is done scanning
+ // the page marks.
+ //
+ // This is accessed atomically.
+ reclaimIndex uint64
+ // reclaimCredit is spare credit for extra pages swept. Since
+ // the page reclaimer works in large chunks, it may reclaim
+ // more than requested. Any spare pages released go to this
+ // credit pool.
+ //
+ // This is accessed atomically.
+ reclaimCredit uintptr
+
// Malloc stats.
largealloc uint64 // bytes allocated for large objects
nlargealloc uint64 // number of large object allocations
@@ -133,21 +151,35 @@ type mheap struct {
// (the actual arenas). This is only used on 32-bit.
arena linearAlloc
- //_ uint32 // ensure 64-bit alignment of central
+ // allArenas is the arenaIndex of every mapped arena. This can
+ // be used to iterate through the address space.
+ //
+ // Access is protected by mheap_.lock. However, since this is
+ // append-only and old backing arrays are never freed, it is
+ // safe to acquire mheap_.lock, copy the slice header, and
+ // then release mheap_.lock.
+ allArenas []arenaIdx
+
+ // sweepArenas is a snapshot of allArenas taken at the
+ // beginning of the sweep cycle. This can be read safely by
+ // simply blocking GC (by disabling preemption).
+ sweepArenas []arenaIdx
+
+ _ uint32 // ensure 64-bit alignment of central
// central free lists for small size classes.
- // the padding makes sure that the MCentrals are
- // spaced CacheLineSize bytes apart, so that each MCentral.lock
+ // the padding makes sure that the mcentrals are
+ // spaced CacheLinePadSize bytes apart, so that each mcentral.lock
// gets its own cache line.
// central is indexed by spanClass.
central [numSpanClasses]struct {
mcentral mcentral
- pad [sys.CacheLineSize - unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte
+ pad [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
}
spanalloc fixalloc // allocator for span*
cachealloc fixalloc // allocator for mcache*
- treapalloc fixalloc // allocator for treapNodes* used by large objects
+ treapalloc fixalloc // allocator for treapNodes*
specialfinalizeralloc fixalloc // allocator for specialfinalizer*
specialprofilealloc fixalloc // allocator for specialprofile*
speciallock mutex // lock for special record allocators.
@@ -184,6 +216,29 @@ type heapArena struct {
// must not be a safe-point between establishing that an
// address is live and looking it up in the spans array.
spans [pagesPerArena]*mspan
+
+ // pageInUse is a bitmap that indicates which spans are in
+ // state mSpanInUse. This bitmap is indexed by page number,
+ // but only the bit corresponding to the first page in each
+ // span is used.
+ //
+ // Writes are protected by mheap_.lock.
+ pageInUse [pagesPerArena / 8]uint8
+
+ // pageMarks is a bitmap that indicates which spans have any
+ // marked objects on them. Like pageInUse, only the bit
+ // corresponding to the first page in each span is used.
+ //
+ // Writes are done atomically during marking. Reads are
+ // non-atomic and lock-free since they only occur during
+ // sweeping (and hence never race with writes).
+ //
+ // This is used to quickly find whole spans that can be freed.
+ //
+ // TODO(austin): It would be nice if this was uint64 for
+ // faster scanning, but we don't have 64-bit atomic bit
+ // operations.
+ pageMarks [pagesPerArena / 8]uint8
}
// arenaHint is a hint for where to grow the heap arenas. See
@@ -196,20 +251,21 @@ type arenaHint struct {
next *arenaHint
}
-// An MSpan is a run of pages.
+// An mspan is a run of pages.
//
-// When a MSpan is in the heap free list, state == MSpanFree
+// When a mspan is in the heap free treap, state == mSpanFree
// and heapmap(s->start) == span, heapmap(s->start+s->npages-1) == span.
+// If the mspan is in the heap scav treap, then in addition to the
+// above scavenged == true. scavenged == false in all other cases.
//
-// When a MSpan is allocated, state == MSpanInUse or MSpanManual
+// When a mspan is allocated, state == mSpanInUse or mSpanManual
// and heapmap(i) == span for all s->start <= i < s->start+s->npages.
-// Every MSpan is in one doubly-linked list,
-// either one of the MHeap's free lists or one of the
-// MCentral's span lists.
+// Every mspan is in one doubly-linked list, either in the mheap's
+// busy list or one of the mcentral's span lists.
-// An MSpan representing actual memory has state _MSpanInUse,
-// _MSpanManual, or _MSpanFree. Transitions between these states are
+// An mspan representing actual memory has state mSpanInUse,
+// mSpanManual, or mSpanFree. Transitions between these states are
// constrained as follows:
//
// * A span may transition from free to in-use or manual during any GC
@@ -225,19 +281,19 @@ type arenaHint struct {
type mSpanState uint8
const (
- _MSpanDead mSpanState = iota
- _MSpanInUse // allocated for garbage collected heap
- _MSpanManual // allocated for manual management (e.g., stack allocator)
- _MSpanFree
+ mSpanDead mSpanState = iota
+ mSpanInUse // allocated for garbage collected heap
+ mSpanManual // allocated for manual management (e.g., stack allocator)
+ mSpanFree
)
// mSpanStateNames are the names of the span states, indexed by
// mSpanState.
var mSpanStateNames = []string{
- "_MSpanDead",
- "_MSpanInUse",
- "_MSpanManual",
- "_MSpanFree",
+ "mSpanDead",
+ "mSpanInUse",
+ "mSpanManual",
+ "mSpanFree",
}
// mSpanList heads a linked list of spans.
@@ -257,7 +313,7 @@ type mspan struct {
startAddr uintptr // address of first byte of span aka s.base()
npages uintptr // number of pages in span
- manualFreeList gclinkptr // list of free objects in _MSpanManual spans
+ manualFreeList gclinkptr // list of free objects in mSpanManual spans
// freeindex is the slot index between 0 and nelems at which to begin scanning
// for the next free object in this span.
@@ -316,6 +372,8 @@ type mspan struct {
// if sweepgen == h->sweepgen - 2, the span needs sweeping
// if sweepgen == h->sweepgen - 1, the span is currently being swept
// if sweepgen == h->sweepgen, the span is swept and ready to use
+ // if sweepgen == h->sweepgen + 1, the span was cached before sweep began and is still cached, and needs sweeping
+ // if sweepgen == h->sweepgen + 3, the span was swept and then cached and is still cached
// h->sweepgen is incremented by 2 after every GC
sweepgen uint32
@@ -323,14 +381,13 @@ type mspan struct {
baseMask uint16 // if non-0, elemsize is a power of 2, & this will get object allocation base
allocCount uint16 // number of allocated objects
spanclass spanClass // size class and noscan (uint8)
- incache bool // being used by an mcache
state mSpanState // mspaninuse etc
needzero uint8 // needs to be zeroed before allocation
divShift uint8 // for divide by elemsize - divMagic.shift
divShift2 uint8 // for divide by elemsize - divMagic.shift2
+ scavenged bool // whether this span has had its pages released to the OS
elemsize uintptr // computed from sizeclass or from npages
unusedsince int64 // first time spotted by gc in mspanfree state
- npreleased uintptr // number of pages released to the os
limit uintptr // end of data in span
speciallock mutex // guards specials list
specials *special // linked list of special records sorted by offset.
@@ -349,6 +406,45 @@ func (s *mspan) layout() (size, n, total uintptr) {
return
}
+// physPageBounds returns the start and end of the span
+// rounded in to the physical page size.
+func (s *mspan) physPageBounds() (uintptr, uintptr) {
+ start := s.base()
+ end := start + s.npages<<_PageShift
+ if physPageSize > _PageSize {
+ // Round start and end in.
+ start = (start + physPageSize - 1) &^ (physPageSize - 1)
+ end &^= physPageSize - 1
+ }
+ return start, end
+}
+
+func (s *mspan) scavenge() uintptr {
+ // start and end must be rounded in, otherwise madvise
+ // will round them *out* and release more memory
+ // than we want.
+ start, end := s.physPageBounds()
+ if end <= start {
+ // start and end don't span a whole physical page.
+ return 0
+ }
+ released := end - start
+ memstats.heap_released += uint64(released)
+ s.scavenged = true
+ sysUnused(unsafe.Pointer(start), released)
+ return released
+}
+
+// released returns the number of bytes in this span
+// which were returned back to the OS.
+func (s *mspan) released() uintptr {
+ if !s.scavenged {
+ return 0
+ }
+ start, end := s.physPageBounds()
+ return end - start
+}
+
// recordspan adds a newly allocated span to h.allspans.
//
// This only happens the first time a span is allocated from
@@ -457,7 +553,7 @@ func (i arenaIdx) l2() uint {
}
// inheap reports whether b is a pointer into a (potentially dead) heap object.
-// It returns false for pointers into _MSpanManual spans.
+// It returns false for pointers into mSpanManual spans.
// Non-preemptible because it is used by write barriers.
//go:nowritebarrier
//go:nosplit
@@ -476,7 +572,7 @@ func inHeapOrStack(b uintptr) bool {
return false
}
switch s.state {
- case mSpanInUse, _MSpanManual:
+ case mSpanInUse, mSpanManual:
return b < s.limit
default:
return false
@@ -550,6 +646,16 @@ func spanOfHeap(p uintptr) *mspan {
return s
}
+// pageIndexOf returns the arena, page index, and page mask for pointer p.
+// The caller must ensure p is in the heap.
+func pageIndexOf(p uintptr) (arena *heapArena, pageIdx uintptr, pageMask uint8) {
+ ai := arenaIndex(p)
+ arena = mheap_.arenas[ai.l1()][ai.l2()]
+ pageIdx = ((p / pageSize) / 8) % uintptr(len(arena.pageInUse))
+ pageMask = byte(1 << ((p / pageSize) % 8))
+ return
+}
+
// Initialize the heap.
func (h *mheap) init() {
h.treapalloc.init(unsafe.Sizeof(treapNode{}), nil, nil, &memstats.other_sys)
@@ -569,117 +675,182 @@ func (h *mheap) init() {
h.spanalloc.zero = false
// h->mapcache needs no init
- for i := range h.free {
- h.free[i].init()
- h.busy[i].init()
- }
- h.busylarge.init()
for i := range h.central {
h.central[i].mcentral.init(spanClass(i))
}
}
-// Sweeps spans in list until reclaims at least npages into heap.
-// Returns the actual number of pages reclaimed.
-func (h *mheap) reclaimList(list *mSpanList, npages uintptr) uintptr {
- n := uintptr(0)
- sg := mheap_.sweepgen
-retry:
- for s := list.first; s != nil; s = s.next {
- if s.sweepgen == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) {
- list.remove(s)
- // swept spans are at the end of the list
- list.insertBack(s) // Puts it back on a busy list. s is not in the treap at this point.
- unlock(&h.lock)
- snpages := s.npages
- if s.sweep(false) {
- n += snpages
+// reclaim sweeps and reclaims at least npage pages into the heap.
+// It is called before allocating npage pages to keep growth in check.
+//
+// reclaim implements the page-reclaimer half of the sweeper.
+//
+// h must NOT be locked.
+func (h *mheap) reclaim(npage uintptr) {
+ // This scans pagesPerChunk at a time. Higher values reduce
+ // contention on h.reclaimPos, but increase the minimum
+ // latency of performing a reclaim.
+ //
+ // Must be a multiple of the pageInUse bitmap element size.
+ //
+ // The time required by this can vary a lot depending on how
+ // many spans are actually freed. Experimentally, it can scan
+ // for pages at ~300 GB/ms on a 2.6GHz Core i7, but can only
+ // free spans at ~32 MB/ms. Using 512 pages bounds this at
+ // roughly 100µs.
+ //
+ // TODO(austin): Half of the time spent freeing spans is in
+ // locking/unlocking the heap (even with low contention). We
+ // could make the slow path here several times faster by
+ // batching heap frees.
+ const pagesPerChunk = 512
+
+ // Bail early if there's no more reclaim work.
+ if atomic.Load64(&h.reclaimIndex) >= 1<<63 {
+ return
+ }
+
+ // Disable preemption so the GC can't start while we're
+ // sweeping, so we can read h.sweepArenas, and so
+ // traceGCSweepStart/Done pair on the P.
+ mp := acquirem()
+
+ if trace.enabled {
+ traceGCSweepStart()
+ }
+
+ arenas := h.sweepArenas
+ locked := false
+ for npage > 0 {
+ // Pull from accumulated credit first.
+ if credit := atomic.Loaduintptr(&h.reclaimCredit); credit > 0 {
+ take := credit
+ if take > npage {
+ // Take only what we need.
+ take = npage
}
- lock(&h.lock)
- if n >= npages {
- return n
+ if atomic.Casuintptr(&h.reclaimCredit, credit, credit-take) {
+ npage -= take
}
- // the span could have been moved elsewhere
- goto retry
- }
- if s.sweepgen == sg-1 {
- // the span is being swept by background sweeper, skip
continue
}
- // already swept empty span,
- // all subsequent ones must also be either swept or in process of sweeping
- break
- }
- return n
-}
-// Sweeps and reclaims at least npage pages into heap.
-// Called before allocating npage pages.
-func (h *mheap) reclaim(npage uintptr) {
- // First try to sweep busy spans with large objects of size >= npage,
- // this has good chances of reclaiming the necessary space.
- for i := int(npage); i < len(h.busy); i++ {
- if h.reclaimList(&h.busy[i], npage) != 0 {
- return // Bingo!
+ // Claim a chunk of work.
+ idx := uintptr(atomic.Xadd64(&h.reclaimIndex, pagesPerChunk) - pagesPerChunk)
+ if idx/pagesPerArena >= uintptr(len(arenas)) {
+ // Page reclaiming is done.
+ atomic.Store64(&h.reclaimIndex, 1<<63)
+ break
}
- }
- // Then -- even larger objects.
- if h.reclaimList(&h.busylarge, npage) != 0 {
- return // Bingo!
- }
+ if !locked {
+ // Lock the heap for reclaimChunk.
+ lock(&h.lock)
+ locked = true
+ }
- // Now try smaller objects.
- // One such object is not enough, so we need to reclaim several of them.
- reclaimed := uintptr(0)
- for i := 0; i < int(npage) && i < len(h.busy); i++ {
- reclaimed += h.reclaimList(&h.busy[i], npage-reclaimed)
- if reclaimed >= npage {
- return
+ // Scan this chunk.
+ nfound := h.reclaimChunk(arenas, idx, pagesPerChunk)
+ if nfound <= npage {
+ npage -= nfound
+ } else {
+ // Put spare pages toward global credit.
+ atomic.Xadduintptr(&h.reclaimCredit, nfound-npage)
+ npage = 0
}
}
+ if locked {
+ unlock(&h.lock)
+ }
- // Now sweep everything that is not yet swept.
- unlock(&h.lock)
- for {
- n := sweepone()
- if n == ^uintptr(0) { // all spans are swept
- break
+ if trace.enabled {
+ traceGCSweepDone()
+ }
+ releasem(mp)
+}
+
+// reclaimChunk sweeps unmarked spans that start at page indexes [pageIdx, pageIdx+n).
+// It returns the number of pages returned to the heap.
+//
+// h.lock must be held and the caller must be non-preemptible.
+func (h *mheap) reclaimChunk(arenas []arenaIdx, pageIdx, n uintptr) uintptr {
+ // The heap lock must be held because this accesses the
+ // heapArena.spans arrays using potentially non-live pointers.
+ // In particular, if a span were freed and merged concurrently
+ // with this probing heapArena.spans, it would be possible to
+ // observe arbitrary, stale span pointers.
+ n0 := n
+ var nFreed uintptr
+ sg := h.sweepgen
+ for n > 0 {
+ ai := arenas[pageIdx/pagesPerArena]
+ ha := h.arenas[ai.l1()][ai.l2()]
+
+ // Get a chunk of the bitmap to work on.
+ arenaPage := uint(pageIdx % pagesPerArena)
+ inUse := ha.pageInUse[arenaPage/8:]
+ marked := ha.pageMarks[arenaPage/8:]
+ if uintptr(len(inUse)) > n/8 {
+ inUse = inUse[:n/8]
+ marked = marked[:n/8]
}
- reclaimed += n
- if reclaimed >= npage {
- break
+
+ // Scan this bitmap chunk for spans that are in-use
+ // but have no marked objects on them.
+ for i := range inUse {
+ inUseUnmarked := inUse[i] &^ marked[i]
+ if inUseUnmarked == 0 {
+ continue
+ }
+
+ for j := uint(0); j < 8; j++ {
+ if inUseUnmarked&(1<<j) != 0 {
+ s := ha.spans[arenaPage+uint(i)*8+j]
+ if atomic.Load(&s.sweepgen) == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) {
+ npages := s.npages
+ unlock(&h.lock)
+ if s.sweep(false) {
+ nFreed += npages
+ }
+ lock(&h.lock)
+ // Reload inUse. It's possible nearby
+ // spans were freed when we dropped the
+ // lock and we don't want to get stale
+ // pointers from the spans array.
+ inUseUnmarked = inUse[i] &^ marked[i]
+ }
+ }
+ }
}
+
+ // Advance.
+ pageIdx += uintptr(len(inUse) * 8)
+ n -= uintptr(len(inUse) * 8)
}
- lock(&h.lock)
+ if trace.enabled {
+ // Account for pages scanned but not reclaimed.
+ traceGCSweepSpan((n0 - nFreed) * pageSize)
+ }
+ return nFreed
}
-// Allocate a new span of npage pages from the heap for GC'd memory
-// and record its size class in the HeapMap and HeapMapCache.
+// alloc_m is the internal implementation of mheap.alloc.
+//
+// alloc_m must run on the system stack because it locks the heap, so
+// any stack growth during alloc_m would self-deadlock.
+//
+//go:systemstack
func (h *mheap) alloc_m(npage uintptr, spanclass spanClass, large bool) *mspan {
_g_ := getg()
- lock(&h.lock)
// To prevent excessive heap growth, before allocating n pages
// we need to sweep and reclaim at least n pages.
if h.sweepdone == 0 {
- // TODO(austin): This tends to sweep a large number of
- // spans in order to find a few completely free spans
- // (for example, in the garbage benchmark, this sweeps
- // ~30x the number of pages its trying to allocate).
- // If GC kept a bit for whether there were any marks
- // in a span, we could release these free spans
- // at the end of GC and eliminate this entirely.
- if trace.enabled {
- traceGCSweepStart()
- }
h.reclaim(npage)
- if trace.enabled {
- traceGCSweepDone()
- }
}
+ lock(&h.lock)
// transfer stats from cache to global
memstats.heap_scan += uint64(_g_.m.mcache.local_scan)
_g_.m.mcache.local_scan = 0
@@ -692,7 +863,7 @@ func (h *mheap) alloc_m(npage uintptr, spanclass spanClass, large bool) *mspan {
// able to map interior pointer to containing span.
atomic.Store(&s.sweepgen, h.sweepgen)
h.sweepSpans[h.sweepgen/2%2].push(s) // Add to swept in-use list.
- s.state = _MSpanInUse
+ s.state = mSpanInUse
s.allocCount = 0
s.spanclass = spanclass
if sizeclass := spanclass.sizeclass(); sizeclass == 0 {
@@ -710,6 +881,10 @@ func (h *mheap) alloc_m(npage uintptr, spanclass spanClass, large bool) *mspan {
s.baseMask = m.baseMask
}
+ // Mark in-use span in arena page bitmap.
+ arena, pageIdx, pageMask := pageIndexOf(s.base())
+ arena.pageInUse[pageIdx] |= pageMask
+
// update stats, sweep lists
h.pagesInUse += uint64(npage)
if large {
@@ -717,12 +892,6 @@ func (h *mheap) alloc_m(npage uintptr, spanclass spanClass, large bool) *mspan {
mheap_.largealloc += uint64(s.elemsize)
mheap_.nlargealloc++
atomic.Xadd64(&memstats.heap_live, int64(npage<<_PageShift))
- // Swept spans are at the end of lists.
- if s.npages < uintptr(len(h.busy)) {
- h.busy[s.npages].insertBack(s)
- } else {
- h.busylarge.insertBack(s)
- }
}
}
// heap_scan and heap_live were updated.
@@ -747,6 +916,12 @@ func (h *mheap) alloc_m(npage uintptr, spanclass spanClass, large bool) *mspan {
return s
}
+// alloc allocates a new span of npage pages from the GC'd heap.
+//
+// Either large must be true or spanclass must indicates the span's
+// size class and scannability.
+//
+// If needzero is true, the memory for the returned span will be zeroed.
func (h *mheap) alloc(npage uintptr, spanclass spanClass, large bool, needzero bool) *mspan {
// Don't do any operations that lock the heap on the G stack.
// It might trigger stack growth, and the stack growth code needs
@@ -784,7 +959,7 @@ func (h *mheap) allocManual(npage uintptr, stat *uint64) *mspan {
lock(&h.lock)
s := h.allocSpanLocked(npage, stat)
if s != nil {
- s.state = _MSpanManual
+ s.state = mSpanManual
s.manualFreeList = 0
s.allocCount = 0
s.spanclass = 0
@@ -823,48 +998,61 @@ func (h *mheap) setSpans(base, npage uintptr, s *mspan) {
}
}
+// pickFreeSpan acquires a free span from internal free list
+// structures if one is available. Otherwise returns nil.
+// h must be locked.
+func (h *mheap) pickFreeSpan(npage uintptr) *mspan {
+ tf := h.free.find(npage)
+ ts := h.scav.find(npage)
+
+ // Check for whichever treap gave us the smaller, non-nil result.
+ // Note that we want the _smaller_ free span, i.e. the free span
+ // closer in size to the amount we requested (npage).
+ var s *mspan
+ if tf != nil && (ts == nil || tf.spanKey.npages <= ts.spanKey.npages) {
+ s = tf.spanKey
+ h.free.removeNode(tf)
+ } else if ts != nil && (tf == nil || tf.spanKey.npages > ts.spanKey.npages) {
+ s = ts.spanKey
+ h.scav.removeNode(ts)
+ }
+ return s
+}
+
// Allocates a span of the given size. h must be locked.
// The returned span has been removed from the
-// free list, but its state is still MSpanFree.
+// free structures, but its state is still mSpanFree.
func (h *mheap) allocSpanLocked(npage uintptr, stat *uint64) *mspan {
- var list *mSpanList
var s *mspan
- // Try in fixed-size lists up to max.
- for i := int(npage); i < len(h.free); i++ {
- list = &h.free[i]
- if !list.isEmpty() {
- s = list.first
- list.remove(s)
- goto HaveSpan
- }
+ s = h.pickFreeSpan(npage)
+ if s != nil {
+ goto HaveSpan
}
- // Best fit in list of large spans.
- s = h.allocLarge(npage) // allocLarge removed s from h.freelarge for us
- if s == nil {
- if !h.grow(npage) {
- return nil
- }
- s = h.allocLarge(npage)
- if s == nil {
- return nil
- }
+ // On failure, grow the heap and try again.
+ if !h.grow(npage) {
+ return nil
+ }
+ s = h.pickFreeSpan(npage)
+ if s != nil {
+ goto HaveSpan
}
+ throw("grew heap, but no adequate free span found")
HaveSpan:
// Mark span in use.
- if s.state != _MSpanFree {
- throw("MHeap_AllocLocked - MSpan not free")
+ if s.state != mSpanFree {
+ throw("candidate mspan for allocation is not free")
}
if s.npages < npage {
- throw("MHeap_AllocLocked - bad npages")
- }
- if s.npreleased > 0 {
- sysUsed(unsafe.Pointer(s.base()), s.npages<<_PageShift)
- memstats.heap_released -= uint64(s.npreleased << _PageShift)
- s.npreleased = 0
+ throw("candidate mspan for allocation is too small")
}
+ // First, subtract any memory that was released back to
+ // the OS from s. We will re-scavenge the trimmed section
+ // if necessary.
+ memstats.heap_released -= uint64(s.released())
+
if s.npages > npage {
// Trim extra and put it back in the heap.
t := (*mspan)(h.spanalloc.alloc())
@@ -874,10 +1062,25 @@ HaveSpan:
h.setSpan(t.base(), t)
h.setSpan(t.base()+t.npages*pageSize-1, t)
t.needzero = s.needzero
- s.state = _MSpanManual // prevent coalescing with s
- t.state = _MSpanManual
+ // If s was scavenged, then t may be scavenged.
+ start, end := t.physPageBounds()
+ if s.scavenged && start < end {
+ memstats.heap_released += uint64(end - start)
+ t.scavenged = true
+ }
+ s.state = mSpanManual // prevent coalescing with s
+ t.state = mSpanManual
h.freeSpanLocked(t, false, false, s.unusedsince)
- s.state = _MSpanFree
+ s.state = mSpanFree
+ }
+ // "Unscavenge" s only AFTER splitting so that
+ // we only sysUsed whatever we actually need.
+ if s.scavenged {
+ // sysUsed all the pages that are actually available
+ // in the span. Note that we don't need to decrement
+ // heap_released since we already did so earlier.
+ sysUsed(unsafe.Pointer(s.base()), s.npages<<_PageShift)
+ s.scavenged = false
}
s.unusedsince = 0
@@ -893,21 +1096,6 @@ HaveSpan:
return s
}
-// Large spans have a minimum size of 1MByte. The maximum number of large spans to support
-// 1TBytes is 1 million, experimentation using random sizes indicates that the depth of
-// the tree is less that 2x that of a perfectly balanced tree. For 1TByte can be referenced
-// by a perfectly balanced tree with a depth of 20. Twice that is an acceptable 40.
-func (h *mheap) isLargeSpan(npages uintptr) bool {
- return npages >= uintptr(len(h.free))
-}
-
-// allocLarge allocates a span of at least npage pages from the treap of large spans.
-// Returns nil if no such span currently exists.
-func (h *mheap) allocLarge(npage uintptr) *mspan {
- // Search treap for smallest span with >= npage pages.
- return h.freelarge.remove(npage)
-}
-
// Try to add at least npage pages of memory to the heap,
// returning whether it worked.
//
@@ -920,20 +1108,31 @@ func (h *mheap) grow(npage uintptr) bool {
return false
}
+ // Scavenge some pages out of the free treap to make up for
+ // the virtual memory space we just allocated. We prefer to
+ // scavenge the largest spans first since the cost of scavenging
+ // is proportional to the number of sysUnused() calls rather than
+ // the number of pages released, so we make fewer of those calls
+ // with larger spans.
+ h.scavengeLargest(size)
+
// Create a fake "in use" span and free it, so that the
// right coalescing happens.
s := (*mspan)(h.spanalloc.alloc())
s.init(uintptr(v), size/pageSize)
h.setSpans(s.base(), s.npages, s)
atomic.Store(&s.sweepgen, h.sweepgen)
- s.state = _MSpanInUse
+ s.state = mSpanInUse
h.pagesInUse += uint64(s.npages)
h.freeSpanLocked(s, false, true, 0)
return true
}
// Free the span back into the heap.
-func (h *mheap) freeSpan(s *mspan, acct int32) {
+//
+// large must match the value of large passed to mheap.alloc. This is
+// used for accounting.
+func (h *mheap) freeSpan(s *mspan, large bool) {
systemstack(func() {
mp := getg().m
lock(&h.lock)
@@ -947,7 +1146,8 @@ func (h *mheap) freeSpan(s *mspan, acct int32) {
bytes := s.npages << _PageShift
msanfree(base, bytes)
}
- if acct != 0 {
+ if large {
+ // Match accounting done in mheap.alloc.
memstats.heap_objects--
}
if gcBlackenEnabled != 0 {
@@ -979,21 +1179,25 @@ func (h *mheap) freeManual(s *mspan, stat *uint64) {
unlock(&h.lock)
}
-// s must be on a busy list (h.busy or h.busylarge) or unlinked.
+// s must be on the busy list or unlinked.
func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince int64) {
switch s.state {
- case _MSpanManual:
+ case mSpanManual:
if s.allocCount != 0 {
- throw("MHeap_FreeSpanLocked - invalid stack free")
+ throw("mheap.freeSpanLocked - invalid stack free")
}
- case _MSpanInUse:
+ case mSpanInUse:
if s.allocCount != 0 || s.sweepgen != h.sweepgen {
- print("MHeap_FreeSpanLocked - span ", s, " ptr ", hex(s.base()), " allocCount ", s.allocCount, " sweepgen ", s.sweepgen, "/", h.sweepgen, "\n")
- throw("MHeap_FreeSpanLocked - invalid free")
+ print("mheap.freeSpanLocked - span ", s, " ptr ", hex(s.base()), " allocCount ", s.allocCount, " sweepgen ", s.sweepgen, "/", h.sweepgen, "\n")
+ throw("mheap.freeSpanLocked - invalid free")
}
h.pagesInUse -= uint64(s.npages)
+
+ // Clear in-use bit in arena page bitmap.
+ arena, pageIdx, pageMask := pageIndexOf(s.base())
+ arena.pageInUse[pageIdx] &^= pageMask
default:
- throw("MHeap_FreeSpanLocked - invalid span state")
+ throw("mheap.freeSpanLocked - invalid span state")
}
if acctinuse {
@@ -1002,10 +1206,7 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i
if acctidle {
memstats.heap_idle += uint64(s.npages << _PageShift)
}
- s.state = _MSpanFree
- if s.inList() {
- h.busyList(s.npages).remove(s)
- }
+ s.state = mSpanFree
// Stamp newly unused spans. The scavenger will use that
// info to potentially give back some pages to the OS.
@@ -1013,133 +1214,122 @@ func (h *mheap) freeSpanLocked(s *mspan, acctinuse, acctidle bool, unusedsince i
if unusedsince == 0 {
s.unusedsince = nanotime()
}
- s.npreleased = 0
+
+ // We scavenge s at the end after coalescing if s or anything
+ // it merged with is marked scavenged.
+ needsScavenge := false
+ prescavenged := s.released() // number of bytes already scavenged.
// Coalesce with earlier, later spans.
- if before := spanOf(s.base() - 1); before != nil && before.state == _MSpanFree {
+ if before := spanOf(s.base() - 1); before != nil && before.state == mSpanFree {
// Now adjust s.
s.startAddr = before.startAddr
s.npages += before.npages
- s.npreleased = before.npreleased // absorb released pages
s.needzero |= before.needzero
h.setSpan(before.base(), s)
+ // If before or s are scavenged, then we need to scavenge the final coalesced span.
+ needsScavenge = needsScavenge || before.scavenged || s.scavenged
+ prescavenged += before.released()
// The size is potentially changing so the treap needs to delete adjacent nodes and
// insert back as a combined node.
- if h.isLargeSpan(before.npages) {
- // We have a t, it is large so it has to be in the treap so we can remove it.
- h.freelarge.removeSpan(before)
+ if before.scavenged {
+ h.scav.removeSpan(before)
} else {
- h.freeList(before.npages).remove(before)
+ h.free.removeSpan(before)
}
- before.state = _MSpanDead
+ before.state = mSpanDead
h.spanalloc.free(unsafe.Pointer(before))
}
// Now check to see if next (greater addresses) span is free and can be coalesced.
- if after := spanOf(s.base() + s.npages*pageSize); after != nil && after.state == _MSpanFree {
+ if after := spanOf(s.base() + s.npages*pageSize); after != nil && after.state == mSpanFree {
s.npages += after.npages
- s.npreleased += after.npreleased
s.needzero |= after.needzero
h.setSpan(s.base()+s.npages*pageSize-1, s)
- if h.isLargeSpan(after.npages) {
- h.freelarge.removeSpan(after)
+ needsScavenge = needsScavenge || after.scavenged || s.scavenged
+ prescavenged += after.released()
+ if after.scavenged {
+ h.scav.removeSpan(after)
} else {
- h.freeList(after.npages).remove(after)
+ h.free.removeSpan(after)
}
- after.state = _MSpanDead
+ after.state = mSpanDead
h.spanalloc.free(unsafe.Pointer(after))
}
- // Insert s into appropriate list or treap.
- if h.isLargeSpan(s.npages) {
- h.freelarge.insert(s)
+ if needsScavenge {
+ // When coalescing spans, some physical pages which
+ // were not returned to the OS previously because
+ // they were only partially covered by the span suddenly
+ // become available for scavenging. We want to make sure
+ // those holes are filled in, and the span is properly
+ // scavenged. Rather than trying to detect those holes
+ // directly, we collect how many bytes were already
+ // scavenged above and subtract that from heap_released
+ // before re-scavenging the entire newly-coalesced span,
+ // which will implicitly bump up heap_released.
+ memstats.heap_released -= uint64(prescavenged)
+ s.scavenge()
+ }
+
+ // Insert s into the appropriate treap.
+ if s.scavenged {
+ h.scav.insert(s)
} else {
- h.freeList(s.npages).insert(s)
- }
-}
-
-func (h *mheap) freeList(npages uintptr) *mSpanList {
- return &h.free[npages]
-}
-
-func (h *mheap) busyList(npages uintptr) *mSpanList {
- if npages < uintptr(len(h.busy)) {
- return &h.busy[npages]
- }
- return &h.busylarge
-}
-
-func scavengeTreapNode(t *treapNode, now, limit uint64) uintptr {
- s := t.spanKey
- var sumreleased uintptr
- if (now-uint64(s.unusedsince)) > limit && s.npreleased != s.npages {
- start := s.base()
- end := start + s.npages<<_PageShift
- if physPageSize > _PageSize {
- // We can only release pages in
- // physPageSize blocks, so round start
- // and end in. (Otherwise, madvise
- // will round them *out* and release
- // more memory than we want.)
- start = (start + physPageSize - 1) &^ (physPageSize - 1)
- end &^= physPageSize - 1
- if end <= start {
- // start and end don't span a
- // whole physical page.
- return sumreleased
- }
- }
- len := end - start
- released := len - (s.npreleased << _PageShift)
- if physPageSize > _PageSize && released == 0 {
- return sumreleased
- }
- memstats.heap_released += uint64(released)
- sumreleased += released
- s.npreleased = len >> _PageShift
- sysUnused(unsafe.Pointer(start), len)
- }
- return sumreleased
-}
-
-func scavengelist(list *mSpanList, now, limit uint64) uintptr {
- if list.isEmpty() {
- return 0
- }
-
- var sumreleased uintptr
- for s := list.first; s != nil; s = s.next {
- if (now-uint64(s.unusedsince)) <= limit || s.npreleased == s.npages {
- continue
+ h.free.insert(s)
+ }
+}
+
+// scavengeLargest scavenges nbytes worth of spans in unscav
+// starting from the largest span and working down. It then takes those spans
+// and places them in scav. h must be locked.
+func (h *mheap) scavengeLargest(nbytes uintptr) {
+ // Iterate over the treap backwards (from largest to smallest) scavenging spans
+ // until we've reached our quota of nbytes.
+ released := uintptr(0)
+ for t := h.free.end(); released < nbytes && t.valid(); {
+ s := t.span()
+ r := s.scavenge()
+ if r == 0 {
+ // Since we're going in order of largest-to-smallest span, this
+ // means all other spans are no bigger than s. There's a high
+ // chance that the other spans don't even cover a full page,
+ // (though they could) but iterating further just for a handful
+ // of pages probably isn't worth it, so just stop here.
+ //
+ // This check also preserves the invariant that spans that have
+ // `scavenged` set are only ever in the `scav` treap, and
+ // those which have it unset are only in the `free` treap.
+ return
}
- start := s.base()
- end := start + s.npages<<_PageShift
- if physPageSize > _PageSize {
- // We can only release pages in
- // physPageSize blocks, so round start
- // and end in. (Otherwise, madvise
- // will round them *out* and release
- // more memory than we want.)
- start = (start + physPageSize - 1) &^ (physPageSize - 1)
- end &^= physPageSize - 1
- if end <= start {
- // start and end don't span a
- // whole physical page.
- continue
+ n := t.prev()
+ h.free.erase(t)
+ t = n
+ h.scav.insert(s)
+ released += r
+ }
+}
+
+// scavengeAll visits each node in the unscav treap and scavenges the
+// treapNode's span. It then removes the scavenged span from
+// unscav and adds it into scav before continuing. h must be locked.
+func (h *mheap) scavengeAll(now, limit uint64) uintptr {
+ // Iterate over the treap scavenging spans if unused for at least limit time.
+ released := uintptr(0)
+ for t := h.free.start(); t.valid(); {
+ s := t.span()
+ n := t.next()
+ if (now - uint64(s.unusedsince)) > limit {
+ r := s.scavenge()
+ if r != 0 {
+ h.free.erase(t)
+ h.scav.insert(s)
+ released += r
}
}
- len := end - start
-
- released := len - (s.npreleased << _PageShift)
- if physPageSize > _PageSize && released == 0 {
- continue
- }
- memstats.heap_released += uint64(released)
- sumreleased += released
- s.npreleased = len >> _PageShift
- sysUnused(unsafe.Pointer(start), len)
+ t = n
}
- return sumreleased
+ return released
}
func (h *mheap) scavenge(k int32, now, limit uint64) {
@@ -1149,17 +1339,13 @@ func (h *mheap) scavenge(k int32, now, limit uint64) {
gp := getg()
gp.m.mallocing++
lock(&h.lock)
- var sumreleased uintptr
- for i := 0; i < len(h.free); i++ {
- sumreleased += scavengelist(&h.free[i], now, limit)
- }
- sumreleased += scavengetreap(h.freelarge.treap, now, limit)
+ released := h.scavengeAll(now, limit)
unlock(&h.lock)
gp.m.mallocing--
if debug.gctrace > 0 {
- if sumreleased > 0 {
- print("scvg", k, ": ", sumreleased>>20, " MB released\n")
+ if released > 0 {
+ print("scvg", k, ": ", released>>20, " MB released\n")
}
print("scvg", k, ": inuse: ", memstats.heap_inuse>>20, ", idle: ", memstats.heap_idle>>20, ", sys: ", memstats.heap_sys>>20, ", released: ", memstats.heap_released>>20, ", consumed: ", (memstats.heap_sys-memstats.heap_released)>>20, " (MB)\n")
}
@@ -1181,11 +1367,10 @@ func (span *mspan) init(base uintptr, npages uintptr) {
span.npages = npages
span.allocCount = 0
span.spanclass = 0
- span.incache = false
span.elemsize = 0
- span.state = _MSpanDead
+ span.state = mSpanDead
span.unusedsince = 0
- span.npreleased = 0
+ span.scavenged = false
span.speciallock.key = 0
span.specials = nil
span.needzero = 0
@@ -1206,9 +1391,9 @@ func (list *mSpanList) init() {
func (list *mSpanList) remove(span *mspan) {
if span.list != list {
- print("runtime: failed MSpanList_Remove span.npages=", span.npages,
+ print("runtime: failed mSpanList.remove span.npages=", span.npages,
" span=", span, " prev=", span.prev, " span.list=", span.list, " list=", list, "\n")
- throw("MSpanList_Remove")
+ throw("mSpanList.remove")
}
if list.first == span {
list.first = span.next
@@ -1231,8 +1416,8 @@ func (list *mSpanList) isEmpty() bool {
func (list *mSpanList) insert(span *mspan) {
if span.next != nil || span.prev != nil || span.list != nil {
- println("runtime: failed MSpanList_Insert", span, span.next, span.prev, span.list)
- throw("MSpanList_Insert")
+ println("runtime: failed mSpanList.insert", span, span.next, span.prev, span.list)
+ throw("mSpanList.insert")
}
span.next = list.first
if list.first != nil {
@@ -1249,8 +1434,8 @@ func (list *mSpanList) insert(span *mspan) {
func (list *mSpanList) insertBack(span *mspan) {
if span.next != nil || span.prev != nil || span.list != nil {
- println("runtime: failed MSpanList_InsertBack", span, span.next, span.prev, span.list)
- throw("MSpanList_InsertBack")
+ println("runtime: failed mSpanList.insertBack", span, span.next, span.prev, span.list)
+ throw("mSpanList.insertBack")
}
span.prev = list.last
if list.last != nil {
@@ -1432,9 +1617,6 @@ func addfinalizer(p unsafe.Pointer, f *funcval, ft *functype, ot *ptrtype) bool
// Mark the finalizer itself, since the
// special isn't part of the GC'd heap.
scanblock(uintptr(unsafe.Pointer(&s.fn)), sys.PtrSize, &oneptrmask[0], gcw)
- if gcBlackenPromptly {
- gcw.dispose()
- }
releasem(mp)
}
return true
@@ -1479,7 +1661,7 @@ func setprofilebucket(p unsafe.Pointer, b *bucket) {
}
// Do whatever cleanup needs to be done to deallocate s. It has
-// already been unlinked from the MSpan specials list.
+// already been unlinked from the mspan specials list.
func freespecial(s *special, p unsafe.Pointer, size uintptr) {
switch s.kind {
case _KindSpecialFinalizer: