]> Cypherpunks.ru repositories - gostls13.git/commitdiff
runtime: separate spans of noscan objects
authorAustin Clements <austin@google.com>
Tue, 9 Feb 2016 22:53:07 +0000 (17:53 -0500)
committerAustin Clements <austin@google.com>
Fri, 28 Apr 2017 22:50:31 +0000 (22:50 +0000)
Currently, we mix objects with pointers and objects without pointers
("noscan" objects) together in memory. As a result, for every object
we grey, we have to check that object's heap bits to find out if it's
noscan, which adds to the per-object cost of GC. This also hurts the
TLB footprint of the garbage collector because it decreases the
density of scannable objects at the page level.

This commit improves the situation by using separate spans for noscan
objects. This will allow a much simpler noscan check (in a follow up
CL), eliminate the need to clear the bitmap of noscan objects (in a
follow up CL), and improves TLB footprint by increasing the density of
scannable objects.

This is also a step toward eliminating dead bits, since the current
noscan check depends on checking the dead bit of the first word.

This has no effect on the heap size of the garbage benchmark.

We'll measure the performance change of this after the follow-up
optimizations.

This is a cherry-pick from dev.garbage commit d491e550c3. The only
non-trivial merge conflict was in updatememstats in mstats.go, where
we now have to separate the per-spanclass stats from the per-sizeclass
stats.

Change-Id: I13bdc4869538ece5649a8d2a41c6605371618e40
Reviewed-on: https://go-review.googlesource.com/41251
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rick Hudson <rlh@golang.org>
src/runtime/export_test.go
src/runtime/malloc.go
src/runtime/mbitmap.go
src/runtime/mcache.go
src/runtime/mcentral.go
src/runtime/mfinal.go
src/runtime/mgcmark.go
src/runtime/mgcsweep.go
src/runtime/mheap.go
src/runtime/mstats.go
src/runtime/stubs.go

index 4bff1bd4540525141e14ada3284a789dc9956874..af91d5291c8d1cbae93c03fd3abc85c36854f802 100644 (file)
@@ -300,13 +300,13 @@ func ReadMemStatsSlow() (base, slow MemStats) {
                        if s.state != mSpanInUse {
                                continue
                        }
-                       if s.sizeclass == 0 {
+                       if sizeclass := s.spanclass.sizeclass(); sizeclass == 0 {
                                slow.Mallocs++
                                slow.Alloc += uint64(s.elemsize)
                        } else {
                                slow.Mallocs += uint64(s.allocCount)
                                slow.Alloc += uint64(s.allocCount) * uint64(s.elemsize)
-                               bySize[s.sizeclass].Mallocs += uint64(s.allocCount)
+                               bySize[sizeclass].Mallocs += uint64(s.allocCount)
                        }
                }
 
index 2e6c3aca0a74cd103d323732149166fad9f44777..db5df3786839f4e1c251e74360163f71c27c7c99 100644 (file)
@@ -518,8 +518,8 @@ func nextFreeFast(s *mspan) gclinkptr {
 // weight allocation. If it is a heavy weight allocation the caller must
 // determine whether a new GC cycle needs to be started or if the GC is active
 // whether this goroutine needs to assist the GC.
-func (c *mcache) nextFree(sizeclass uint8) (v gclinkptr, s *mspan, shouldhelpgc bool) {
-       s = c.alloc[sizeclass]
+func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) {
+       s = c.alloc[spc]
        shouldhelpgc = false
        freeIndex := s.nextFreeIndex()
        if freeIndex == s.nelems {
@@ -529,10 +529,10 @@ func (c *mcache) nextFree(sizeclass uint8) (v gclinkptr, s *mspan, shouldhelpgc
                        throw("s.allocCount != s.nelems && freeIndex == s.nelems")
                }
                systemstack(func() {
-                       c.refill(int32(sizeclass))
+                       c.refill(spc)
                })
                shouldhelpgc = true
-               s = c.alloc[sizeclass]
+               s = c.alloc[spc]
 
                freeIndex = s.nextFreeIndex()
        }
@@ -656,10 +656,10 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
                                return x
                        }
                        // Allocate a new maxTinySize block.
-                       span := c.alloc[tinySizeClass]
+                       span := c.alloc[tinySpanClass]
                        v := nextFreeFast(span)
                        if v == 0 {
-                               v, _, shouldhelpgc = c.nextFree(tinySizeClass)
+                               v, _, shouldhelpgc = c.nextFree(tinySpanClass)
                        }
                        x = unsafe.Pointer(v)
                        (*[2]uint64)(x)[0] = 0
@@ -679,10 +679,11 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
                                sizeclass = size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]
                        }
                        size = uintptr(class_to_size[sizeclass])
-                       span := c.alloc[sizeclass]
+                       spc := makeSpanClass(sizeclass, noscan)
+                       span := c.alloc[spc]
                        v := nextFreeFast(span)
                        if v == 0 {
-                               v, span, shouldhelpgc = c.nextFree(sizeclass)
+                               v, span, shouldhelpgc = c.nextFree(spc)
                        }
                        x = unsafe.Pointer(v)
                        if needzero && span.needzero != 0 {
@@ -693,7 +694,7 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
                var s *mspan
                shouldhelpgc = true
                systemstack(func() {
-                       s = largeAlloc(size, needzero)
+                       s = largeAlloc(size, needzero, noscan)
                })
                s.freeindex = 1
                s.allocCount = 1
@@ -784,7 +785,7 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
        return x
 }
 
-func largeAlloc(size uintptr, needzero bool) *mspan {
+func largeAlloc(size uintptr, needzero bool, noscan bool) *mspan {
        // print("largeAlloc size=", size, "\n")
 
        if size+_PageSize < size {
@@ -800,7 +801,7 @@ func largeAlloc(size uintptr, needzero bool) *mspan {
        // pays the debt down to npage pages.
        deductSweepCredit(npages*_PageSize, npages)
 
-       s := mheap_.alloc(npages, 0, true, needzero)
+       s := mheap_.alloc(npages, makeSpanClass(0, noscan), true, needzero)
        if s == nil {
                throw("out of memory")
        }
index 8075f66115b97ef564d1374ba04e40effcd7dbec..844e662a048b3bd48b00aa17366506a0c44a7b11 100644 (file)
@@ -499,6 +499,7 @@ func (h heapBits) isPointer() bool {
 // It must be told how large the object at h is for efficiency.
 // h must describe the initial word of the object.
 func (h heapBits) hasPointers(size uintptr) bool {
+       // TODO: Use span.noScan instead of the heap bitmap.
        if size == sys.PtrSize { // 1-word objects are always pointers
                return true
        }
index c483310cee7597de88db13658a19c48bd7305078..96fb273337ad1d749ce5a51ac8d47cec367edcf8 100644 (file)
@@ -33,7 +33,8 @@ type mcache struct {
        local_tinyallocs uintptr // number of tiny allocs not counted in other stats
 
        // The rest is not accessed on every malloc.
-       alloc [_NumSizeClasses]*mspan // spans to allocate from
+
+       alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass
 
        stackcache [_NumStackOrders]stackfreelist
 
@@ -77,7 +78,7 @@ func allocmcache() *mcache {
        lock(&mheap_.lock)
        c := (*mcache)(mheap_.cachealloc.alloc())
        unlock(&mheap_.lock)
-       for i := 0; i < _NumSizeClasses; i++ {
+       for i := range c.alloc {
                c.alloc[i] = &emptymspan
        }
        c.next_sample = nextSample()
@@ -103,12 +104,12 @@ func freemcache(c *mcache) {
 
 // Gets a span that has a free object in it and assigns it
 // to be the cached span for the given sizeclass. Returns this span.
-func (c *mcache) refill(sizeclass int32) *mspan {
+func (c *mcache) refill(spc spanClass) *mspan {
        _g_ := getg()
 
        _g_.m.locks++
        // Return the current cached span to the central lists.
-       s := c.alloc[sizeclass]
+       s := c.alloc[spc]
 
        if uintptr(s.allocCount) != s.nelems {
                throw("refill of span with free space remaining")
@@ -119,7 +120,7 @@ func (c *mcache) refill(sizeclass int32) *mspan {
        }
 
        // Get a new cached span from the central lists.
-       s = mheap_.central[sizeclass].mcentral.cacheSpan()
+       s = mheap_.central[spc].mcentral.cacheSpan()
        if s == nil {
                throw("out of memory")
        }
@@ -128,13 +129,13 @@ func (c *mcache) refill(sizeclass int32) *mspan {
                throw("span has no free space")
        }
 
-       c.alloc[sizeclass] = s
+       c.alloc[spc] = s
        _g_.m.locks--
        return s
 }
 
 func (c *mcache) releaseAll() {
-       for i := 0; i < _NumSizeClasses; i++ {
+       for i := range c.alloc {
                s := c.alloc[i]
                if s != &emptymspan {
                        mheap_.central[i].mcentral.uncacheSpan(s)
index 5302dd8e3df6a851708fe059e64653be980b709b..eaabcb9c2993b6853ee713902a34f5317eb3f80f 100644 (file)
@@ -19,7 +19,7 @@ import "runtime/internal/atomic"
 //go:notinheap
 type mcentral struct {
        lock      mutex
-       sizeclass int32
+       spanclass spanClass
        nonempty  mSpanList // list of spans with a free object, ie a nonempty free list
        empty     mSpanList // list of spans with no free objects (or cached in an mcache)
 
@@ -30,8 +30,8 @@ type mcentral struct {
 }
 
 // Initialize a single central free list.
-func (c *mcentral) init(sizeclass int32) {
-       c.sizeclass = sizeclass
+func (c *mcentral) init(spc spanClass) {
+       c.spanclass = spc
        c.nonempty.init()
        c.empty.init()
 }
@@ -39,7 +39,7 @@ func (c *mcentral) init(sizeclass int32) {
 // Allocate a span to use in an MCache.
 func (c *mcentral) cacheSpan() *mspan {
        // Deduct credit for this span allocation and sweep if necessary.
-       spanBytes := uintptr(class_to_allocnpages[c.sizeclass]) * _PageSize
+       spanBytes := uintptr(class_to_allocnpages[c.spanclass.sizeclass()]) * _PageSize
        deductSweepCredit(spanBytes, 0)
 
        lock(&c.lock)
@@ -225,11 +225,11 @@ func (c *mcentral) freeSpan(s *mspan, preserve bool, wasempty bool) bool {
 
 // grow allocates a new empty span from the heap and initializes it for c's size class.
 func (c *mcentral) grow() *mspan {
-       npages := uintptr(class_to_allocnpages[c.sizeclass])
-       size := uintptr(class_to_size[c.sizeclass])
+       npages := uintptr(class_to_allocnpages[c.spanclass.sizeclass()])
+       size := uintptr(class_to_size[c.spanclass.sizeclass()])
        n := (npages << _PageShift) / size
 
-       s := mheap_.alloc(npages, c.sizeclass, false, true)
+       s := mheap_.alloc(npages, c.spanclass, false, true)
        if s == nil {
                return nil
        }
index 6ba132288135f7aac9045936dcc8165b25b1db3b..a8729b1aa469a8f14312d1d7ee30e1b69bd9499d 100644 (file)
@@ -455,7 +455,7 @@ func findObject(v unsafe.Pointer) (s *mspan, x unsafe.Pointer, n uintptr) {
        }
 
        n = s.elemsize
-       if s.sizeclass != 0 {
+       if s.spanclass.sizeclass() != 0 {
                x = add(x, (uintptr(v)-uintptr(x))/n*n)
        }
        return
index f330c1a66896f74d0734e7943a54997094f72a02..1046aa896e1b05550820ba00e6ed38d402682749 100644 (file)
@@ -1290,7 +1290,7 @@ func gcDumpObject(label string, obj, off uintptr) {
                print(" s=nil\n")
                return
        }
-       print(" s.base()=", hex(s.base()), " s.limit=", hex(s.limit), " s.sizeclass=", s.sizeclass, " s.elemsize=", s.elemsize, " s.state=")
+       print(" s.base()=", hex(s.base()), " s.limit=", hex(s.limit), " s.spanclass=", s.spanclass, " s.elemsize=", s.elemsize, " s.state=")
        if 0 <= s.state && int(s.state) < len(mSpanStateNames) {
                print(mSpanStateNames[s.state], "\n")
        } else {
index 102d734c4deb2bb15d1e0fa3ba4dd051daada17d..1bb19ec689c550e8fbe34edec7301382d6b08b6b 100644 (file)
@@ -195,7 +195,7 @@ func (s *mspan) sweep(preserve bool) bool {
 
        atomic.Xadd64(&mheap_.pagesSwept, int64(s.npages))
 
-       cl := s.sizeclass
+       spc := s.spanclass
        size := s.elemsize
        res := false
 
@@ -288,7 +288,7 @@ func (s *mspan) sweep(preserve bool) bool {
 
        // Count the number of free objects in this span.
        nalloc := uint16(s.countAlloc())
-       if cl == 0 && nalloc == 0 {
+       if spc.sizeclass() == 0 && nalloc == 0 {
                s.needzero = 1
                freeToHeap = true
        }
@@ -331,9 +331,9 @@ func (s *mspan) sweep(preserve bool) bool {
                atomic.Store(&s.sweepgen, sweepgen)
        }
 
-       if nfreed > 0 && cl != 0 {
-               c.local_nsmallfree[cl] += uintptr(nfreed)
-               res = mheap_.central[cl].mcentral.freeSpan(s, preserve, wasempty)
+       if nfreed > 0 && spc.sizeclass() != 0 {
+               c.local_nsmallfree[spc.sizeclass()] += uintptr(nfreed)
+               res = mheap_.central[spc].mcentral.freeSpan(s, preserve, wasempty)
                // MCentral_FreeSpan updates sweepgen
        } else if freeToHeap {
                // Free large span to heap
index 82dc599b975926713cc95ee60f869526862fa308..f237ec26aae3f8c8ad6dc797580a05ea1c5aed03 100644 (file)
@@ -119,7 +119,8 @@ type mheap struct {
        // the padding makes sure that the MCentrals are
        // spaced CacheLineSize bytes apart, so that each MCentral.lock
        // gets its own cache line.
-       central [_NumSizeClasses]struct {
+       // central is indexed by spanClass.
+       central [numSpanClasses]struct {
                mcentral mcentral
                pad      [sys.CacheLineSize - unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte
        }
@@ -260,7 +261,7 @@ type mspan struct {
        divMul      uint16     // for divide by elemsize - divMagic.mul
        baseMask    uint16     // if non-0, elemsize is a power of 2, & this will get object allocation base
        allocCount  uint16     // number of allocated objects
-       sizeclass   uint8      // size class
+       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
@@ -315,6 +316,31 @@ func recordspan(vh unsafe.Pointer, p unsafe.Pointer) {
        h.allspans = append(h.allspans, s)
 }
 
+// A spanClass represents the size class and noscan-ness of a span.
+//
+// Each size class has a noscan spanClass and a scan spanClass. The
+// noscan spanClass contains only noscan objects, which do not contain
+// pointers and thus do not need to be scanned by the garbage
+// collector.
+type spanClass uint8
+
+const (
+       numSpanClasses = _NumSizeClasses << 1
+       tinySpanClass  = tinySizeClass<<1 | 1
+)
+
+func makeSpanClass(sizeclass uint8, noscan bool) spanClass {
+       return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))
+}
+
+func (sc spanClass) sizeclass() int8 {
+       return int8(sc >> 1)
+}
+
+func (sc spanClass) noscan() bool {
+       return sc&1 != 0
+}
+
 // inheap reports whether b is a pointer into a (potentially dead) heap object.
 // It returns false for pointers into _MSpanManual spans.
 // Non-preemptible because it is used by write barriers.
@@ -399,7 +425,7 @@ func mlookup(v uintptr, base *uintptr, size *uintptr, sp **mspan) int32 {
        }
 
        p := s.base()
-       if s.sizeclass == 0 {
+       if s.spanclass.sizeclass() == 0 {
                // Large object.
                if base != nil {
                        *base = p
@@ -447,7 +473,7 @@ func (h *mheap) init(spansStart, spansBytes uintptr) {
 
        h.busylarge.init()
        for i := range h.central {
-               h.central[i].mcentral.init(int32(i))
+               h.central[i].mcentral.init(spanClass(i))
        }
 
        sp := (*slice)(unsafe.Pointer(&h.spans))
@@ -577,7 +603,7 @@ func (h *mheap) reclaim(npage uintptr) {
 
 // Allocate a new span of npage pages from the heap for GC'd memory
 // and record its size class in the HeapMap and HeapMapCache.
-func (h *mheap) alloc_m(npage uintptr, sizeclass int32, large bool) *mspan {
+func (h *mheap) alloc_m(npage uintptr, spanclass spanClass, large bool) *mspan {
        _g_ := getg()
        if _g_ != _g_.m.g0 {
                throw("_mheap_alloc not on g0 stack")
@@ -617,8 +643,8 @@ func (h *mheap) alloc_m(npage uintptr, sizeclass int32, large bool) *mspan {
                h.sweepSpans[h.sweepgen/2%2].push(s) // Add to swept in-use list.
                s.state = _MSpanInUse
                s.allocCount = 0
-               s.sizeclass = uint8(sizeclass)
-               if sizeclass == 0 {
+               s.spanclass = spanclass
+               if sizeclass := spanclass.sizeclass(); sizeclass == 0 {
                        s.elemsize = s.npages << _PageShift
                        s.divShift = 0
                        s.divMul = 0
@@ -670,13 +696,13 @@ func (h *mheap) alloc_m(npage uintptr, sizeclass int32, large bool) *mspan {
        return s
 }
 
-func (h *mheap) alloc(npage uintptr, sizeclass int32, large bool, needzero bool) *mspan {
+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
        // to be able to allocate heap.
        var s *mspan
        systemstack(func() {
-               s = h.alloc_m(npage, sizeclass, large)
+               s = h.alloc_m(npage, spanclass, large)
        })
 
        if s != nil {
@@ -710,7 +736,7 @@ func (h *mheap) allocManual(npage uintptr, stat *uint64) *mspan {
                s.state = _MSpanManual
                s.manualFreeList = 0
                s.allocCount = 0
-               s.sizeclass = 0
+               s.spanclass = 0
                s.nelems = 0
                s.elemsize = 0
                s.limit = s.base() + s.npages<<_PageShift
@@ -1144,7 +1170,7 @@ func (span *mspan) init(base uintptr, npages uintptr) {
        span.startAddr = base
        span.npages = npages
        span.allocCount = 0
-       span.sizeclass = 0
+       span.spanclass = 0
        span.incache = false
        span.elemsize = 0
        span.state = _MSpanDead
index 95824a9c097f2907e299a805c1be92504d9307e6..849e01860b051a6936f6fa77f9413aeb9413be4f 100644 (file)
@@ -552,7 +552,18 @@ func updatememstats() {
        // Collect allocation stats. This is safe and consistent
        // because the world is stopped.
        var smallFree, totalAlloc, totalFree uint64
-       for i := range mheap_.central {
+       // Collect per-spanclass stats.
+       for spc := range mheap_.central {
+               // The mcaches are now empty, so mcentral stats are
+               // up-to-date.
+               c := &mheap_.central[spc].mcentral
+               memstats.nmalloc += c.nmalloc
+               i := spanClass(spc).sizeclass()
+               memstats.by_size[i].nmalloc += c.nmalloc
+               totalAlloc += c.nmalloc * uint64(class_to_size[i])
+       }
+       // Collect per-sizeclass stats.
+       for i := 0; i < _NumSizeClasses; i++ {
                if i == 0 {
                        memstats.nmalloc += mheap_.nlargealloc
                        totalAlloc += mheap_.largealloc
@@ -560,12 +571,6 @@ func updatememstats() {
                        memstats.nfree += mheap_.nlargefree
                        continue
                }
-               // The mcaches are now empty, so mcentral stats are
-               // up-to-date.
-               c := &mheap_.central[i].mcentral
-               memstats.nmalloc += c.nmalloc
-               memstats.by_size[i].nmalloc += c.nmalloc
-               totalAlloc += c.nmalloc * uint64(class_to_size[i])
 
                // The mcache stats have been flushed to mheap_.
                memstats.nfree += mheap_.nsmallfree[i]
index f2139c2a023079a14da09345d463eb6f147c4c33..c4f32a84825c9cdbb81136909f21ac17c9bf557f 100644 (file)
@@ -291,3 +291,10 @@ func checkASM() bool
 
 func memequal_varlen(a, b unsafe.Pointer) bool
 func eqstring(s1, s2 string) bool
+
+// bool2int returns 0 if x is false or 1 if x is true.
+func bool2int(x bool) int {
+       // Avoid branches. In the SSA compiler, this compiles to
+       // exactly what you would want it to.
+       return int(uint8(*(*uint8)(unsafe.Pointer(&x))))
+}