]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: free workbufs during sweeping
authorAustin Clements <austin@google.com>
Mon, 20 Mar 2017 21:25:59 +0000 (17:25 -0400)
committerAustin Clements <austin@google.com>
Thu, 13 Apr 2017 18:20:47 +0000 (18:20 +0000)
This extends the sweeper to free workbufs back to the heap between GC
cycles, allowing this memory to be reused for GC'd allocations or
eventually returned to the OS.

This helps for applications that have high peak heap usage relative to
their regular heap usage (for example, a high-memory initialization
phase). Workbuf memory is roughly proportional to heap size and since
we currently never free workbufs, it's proportional to *peak* heap
size. By freeing workbufs, we can release and reuse this memory for
other purposes when the heap shrinks.

This is somewhat complicated because this costs ~1–2 µs per workbuf
span, so for large heaps it's too expensive to just do synchronously
after mark termination between starting the world and dropping the
worldsema. Hence, we do it asynchronously in the sweeper. This adds a
list of "free" workbuf spans that can be returned to the heap. GC
moves all workbuf spans to this list after mark termination and the
background sweeper drains this list back to the heap. If the sweeper
doesn't finish, that's fine, since getempty can directly reuse any
remaining spans to allocate more workbufs.

Performance impact is negligible. On the x/benchmarks, this reduces
GC-bytes-from-system by 6–11%.

Fixes #19325.

Change-Id: Icb92da2196f0c39ee984faf92d52f29fd9ded7a8
Reviewed-on: https://go-review.googlesource.com/38582
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rick Hudson <rlh@golang.org>
src/runtime/mgc.go
src/runtime/mgcsweep.go
src/runtime/mgcwork.go
src/runtime/mheap.go

index d537aaf67efc2f1eb1c4dbff6f7c6fab45f91548..097b742a7b2dae2ddadbf5ef3c21837cf4252faf 100644 (file)
@@ -797,6 +797,9 @@ var work struct {
 
        wbufSpans struct {
                lock mutex
+               // free is a list of spans dedicated to workbufs, but
+               // that don't currently contain any workbufs.
+               free mSpanList
                // busy is a list of all spans containing workbufs on
                // one of the workbuf lists.
                busy mSpanList
@@ -1480,6 +1483,10 @@ func gcMarkTermination() {
        // world stopped.
        mProf_Flush()
 
+       // Prepare workbufs for freeing by the sweeper. We do this
+       // asynchronously because it can take non-trivial time.
+       prepareFreeWorkbufs()
+
        // Free stack spans. This must be done between GC cycles.
        systemstack(freeStackSpans)
 
@@ -1923,6 +1930,10 @@ func gcSweep(mode gcMode) {
                for sweepone() != ^uintptr(0) {
                        sweep.npausesweep++
                }
+               // Free workbufs eagerly.
+               prepareFreeWorkbufs()
+               for freeSomeWbufs(false) {
+               }
                // All "free" events for this mark/sweep cycle have
                // now happened, so we can make this profile cycle
                // available immediately.
index 474eabda79720ffae95720224db191210e13b8c1..bdd9e517d490116a564d3ee3b2a890e714523fd1 100644 (file)
@@ -56,6 +56,9 @@ func bgsweep(c chan int) {
                        sweep.nbgsweep++
                        Gosched()
                }
+               for freeSomeWbufs(true) {
+                       Gosched()
+               }
                lock(&sweep.lock)
                if !gosweepdone() {
                        // This can happen if a GC runs between
index a9559230dec6ae78fcc8ddb064be03104383db38..461679b93431dea4eed9168521d3eb5589baf4c7 100644 (file)
@@ -334,16 +334,27 @@ func getempty() *workbuf {
        if b == nil {
                // Allocate more workbufs.
                var s *mspan
-               systemstack(func() {
-                       s = mheap_.allocManual(workbufAlloc/pageSize, &memstats.gc_sys)
-               })
+               if work.wbufSpans.free.first != nil {
+                       lock(&work.wbufSpans.lock)
+                       s = work.wbufSpans.free.first
+                       if s != nil {
+                               work.wbufSpans.free.remove(s)
+                               work.wbufSpans.busy.insert(s)
+                       }
+                       unlock(&work.wbufSpans.lock)
+               }
                if s == nil {
-                       throw("out of memory")
+                       systemstack(func() {
+                               s = mheap_.allocManual(workbufAlloc/pageSize, &memstats.gc_sys)
+                       })
+                       if s == nil {
+                               throw("out of memory")
+                       }
+                       // Record the new span in the busy list.
+                       lock(&work.wbufSpans.lock)
+                       work.wbufSpans.busy.insert(s)
+                       unlock(&work.wbufSpans.lock)
                }
-               // Record the new span in the busy list.
-               lock(&work.wbufSpans.lock)
-               work.wbufSpans.busy.insert(s)
-               unlock(&work.wbufSpans.lock)
                // Slice up the span into new workbufs. Return one and
                // put the rest on the empty list.
                for i := uintptr(0); i+_WorkbufSize <= workbufAlloc; i += _WorkbufSize {
@@ -456,3 +467,44 @@ func handoff(b *workbuf) *workbuf {
        putfull(b)
        return b1
 }
+
+// prepareFreeWorkbufs moves busy workbuf spans to free list so they
+// can be freed to the heap. This must only be called when all
+// workbufs are on the empty list.
+func prepareFreeWorkbufs() {
+       lock(&work.wbufSpans.lock)
+       if work.full != 0 {
+               throw("cannot free workbufs when work.full != 0")
+       }
+       // Since all workbufs are on the empty list, we don't care
+       // which ones are in which spans. We can wipe the entire empty
+       // list and move all workbuf spans to the free list.
+       work.empty = 0
+       work.wbufSpans.free.takeAll(&work.wbufSpans.busy)
+       unlock(&work.wbufSpans.lock)
+}
+
+// freeSomeWbufs frees some workbufs back to the heap and returns
+// true if it should be called again to free more.
+func freeSomeWbufs(preemptible bool) bool {
+       const batchSize = 64 // ~1–2 µs per span.
+       lock(&work.wbufSpans.lock)
+       if gcphase != _GCoff || work.wbufSpans.free.isEmpty() {
+               unlock(&work.wbufSpans.lock)
+               return false
+       }
+       systemstack(func() {
+               gp := getg().m.curg
+               for i := 0; i < batchSize && !(preemptible && gp.preempt); i++ {
+                       span := work.wbufSpans.free.first
+                       if span == nil {
+                               break
+                       }
+                       work.wbufSpans.free.remove(span)
+                       mheap_.freeManual(span, &memstats.gc_sys)
+               }
+       })
+       more := !work.wbufSpans.free.isEmpty()
+       unlock(&work.wbufSpans.lock)
+       return more
+}
index bf0ae785a9589937867045a1b5c3305f7ecec785..80d925cac638cb8415d71415a6fcfa7730216a94 100644 (file)
@@ -1204,6 +1204,31 @@ func (list *mSpanList) insertBack(span *mspan) {
        span.list = list
 }
 
+// takeAll removes all spans from other and inserts them at the front
+// of list.
+func (list *mSpanList) takeAll(other *mSpanList) {
+       if other.isEmpty() {
+               return
+       }
+
+       // Reparent everything in other to list.
+       for s := other.first; s != nil; s = s.next {
+               s.list = list
+       }
+
+       // Concatenate the lists.
+       if list.isEmpty() {
+               *list = *other
+       } else {
+               // Neither list is empty. Put other before list.
+               other.last.next = list.first
+               list.first.prev = other.last
+               list.first = other.first
+       }
+
+       other.first, other.last = nil, nil
+}
+
 const (
        _KindSpecialFinalizer = 1
        _KindSpecialProfile   = 2