diff options
author | Ian Lance Taylor <iant@golang.org> | 2019-01-18 19:04:36 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2019-01-18 19:04:36 +0000 |
commit | 4f4a855d82a889cebcfca150a7a43909bcb6a346 (patch) | |
tree | f12bae0781920fa34669fe30b6f4615a86d9fb80 /libgo/go/runtime/mheap.go | |
parent | 225220d668dafb8262db7012bced688acbe63b33 (diff) | |
download | gcc-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.go | 788 |
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: |