1
0
mirror of https://github.com/golang/go synced 2024-11-05 16:26:11 -07:00

runtime: switch to gcWork abstraction

This converts the garbage collector from directly manipulating work
buffers to using the new gcWork abstraction.

The previous management of work buffers was rather ad hoc.  As a
result, switching to the gcWork abstraction changes many details of
work buffer management.

If greyobject fills a work buffer, it can now pull from work.partial
in addition to work.empty.

Previously, gcDrain started with a partial or empty work buffer and
fetched an empty work buffer if it filled its current buffer (in
greyobject).  Now, gcDrain starts with a full work buffer and fetches
an partial or empty work buffer if it fills its current buffer (in
greyobject).  The original behavior was bad because gcDrain would
immediately drop the empty work buffer returned by greyobject and
fetch a full work buffer, which greyobject was likely to immediately
overflow, fetching another empty work buffer, etc.  The new behavior
isn't great at the start because greyobject is likely to immediately
overflow the full buffer, but the steady-state behavior should be more
stable.  Both before and after this change, gcDrain fetches a full
work buffer if it drains its current buffer.  Basically all of these
choices are bad; the right answer is to use a dual work buffer scheme.

Previously, shade always fetched a work buffer (though usually from
m.currentwbuf), even if the object was already marked.  Now it only
fetches a work buffer if it actually greys an object.

Change-Id: I8b880ed660eb63135236fa5d5678f0c1c041881f
Reviewed-on: https://go-review.googlesource.com/5232
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Rick Hudson <rlh@golang.org>
This commit is contained in:
Austin Clements 2015-02-17 10:53:31 -05:00
parent b30d19de59
commit 02dcdba7c8

View File

@ -257,7 +257,7 @@ func gcmarknewobject_m(obj uintptr) {
// Return possibly new workbuf to use. // Return possibly new workbuf to use.
// base and off are for debugging only and could be removed. // base and off are for debugging only and could be removed.
//go:nowritebarrier //go:nowritebarrier
func greyobject(obj, base, off uintptr, hbits heapBits, wbuf *workbuf) *workbuf { func greyobject(obj, base, off uintptr, hbits heapBits, gcw *gcWorkProducer) {
// obj should be start of allocation, and so must be at least pointer-aligned. // obj should be start of allocation, and so must be at least pointer-aligned.
if obj&(ptrSize-1) != 0 { if obj&(ptrSize-1) != 0 {
throw("greyobject: obj not pointer-aligned") throw("greyobject: obj not pointer-aligned")
@ -307,7 +307,7 @@ func greyobject(obj, base, off uintptr, hbits heapBits, wbuf *workbuf) *workbuf
throw("checkmark found unmarked object") throw("checkmark found unmarked object")
} }
if !hbits.isCheckmarked() { if !hbits.isCheckmarked() {
return wbuf return
} }
hbits.setCheckmarked() hbits.setCheckmarked()
if !hbits.isCheckmarked() { if !hbits.isCheckmarked() {
@ -316,7 +316,7 @@ func greyobject(obj, base, off uintptr, hbits heapBits, wbuf *workbuf) *workbuf
} else { } else {
// If marked we have nothing to do. // If marked we have nothing to do.
if hbits.isMarked() { if hbits.isMarked() {
return wbuf return
} }
// Each byte of GC bitmap holds info for two words. // Each byte of GC bitmap holds info for two words.
@ -327,7 +327,7 @@ func greyobject(obj, base, off uintptr, hbits heapBits, wbuf *workbuf) *workbuf
} }
if !checkmarkphase && hbits.typeBits() == typeDead { if !checkmarkphase && hbits.typeBits() == typeDead {
return wbuf // noscan object return // noscan object
} }
// Queue the obj for scanning. The PREFETCH(obj) logic has been removed but // Queue the obj for scanning. The PREFETCH(obj) logic has been removed but
@ -337,15 +337,7 @@ func greyobject(obj, base, off uintptr, hbits heapBits, wbuf *workbuf) *workbuf
// to give the PREFETCH time to do its work. // to give the PREFETCH time to do its work.
// Use of PREFETCHNTA might be more appropriate than PREFETCH // Use of PREFETCHNTA might be more appropriate than PREFETCH
// If workbuf is full, obtain an empty one. gcw.put(obj)
if wbuf.nobj >= uintptr(len(wbuf.obj)) {
putfull(wbuf, 358)
wbuf = getempty(359)
}
wbuf.obj[wbuf.nobj] = obj
wbuf.nobj++
return wbuf
} }
// Scan the object b of size n, adding pointers to wbuf. // Scan the object b of size n, adding pointers to wbuf.
@ -355,7 +347,7 @@ func greyobject(obj, base, off uintptr, hbits heapBits, wbuf *workbuf) *workbuf
// In this case, n may be an overestimate of the size; the GC bitmap // In this case, n may be an overestimate of the size; the GC bitmap
// must also be used to make sure the scan stops at the end of b. // must also be used to make sure the scan stops at the end of b.
//go:nowritebarrier //go:nowritebarrier
func scanobject(b, n uintptr, ptrmask *uint8, wbuf *workbuf) *workbuf { func scanobject(b, n uintptr, ptrmask *uint8, gcw *gcWorkProducer) {
arena_start := mheap_.arena_start arena_start := mheap_.arena_start
arena_used := mheap_.arena_used arena_used := mheap_.arena_used
@ -364,7 +356,7 @@ func scanobject(b, n uintptr, ptrmask *uint8, wbuf *workbuf) *workbuf {
if ptrmask == nil { if ptrmask == nil {
b, hbits = heapBitsForObject(b) b, hbits = heapBitsForObject(b)
if b == 0 { if b == 0 {
return wbuf return
} }
if n == 0 { if n == 0 {
n = mheap_.arena_used - b n = mheap_.arena_used - b
@ -413,16 +405,15 @@ func scanobject(b, n uintptr, ptrmask *uint8, wbuf *workbuf) *workbuf {
// Mark the object. // Mark the object.
if obj, hbits := heapBitsForObject(obj); obj != 0 { if obj, hbits := heapBitsForObject(obj); obj != 0 {
wbuf = greyobject(obj, b, i, hbits, wbuf) greyobject(obj, b, i, hbits, gcw)
} }
} }
return wbuf
} }
// scanblock scans b as scanobject would. // scanblock scans b as scanobject would.
// If the gcphase is GCscan, scanblock performs additional checks. // If the gcphase is GCscan, scanblock performs additional checks.
//go:nowritebarrier //go:nowritebarrier
func scanblock(b0, n0 uintptr, ptrmask *uint8, wbuf *workbuf) *workbuf { func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWorkProducer) {
// Use local copies of original parameters, so that a stack trace // Use local copies of original parameters, so that a stack trace
// due to one of the throws below shows the original block // due to one of the throws below shows the original block
// base and extent. // base and extent.
@ -433,103 +424,78 @@ func scanblock(b0, n0 uintptr, ptrmask *uint8, wbuf *workbuf) *workbuf {
// 1. nil - obtain pointer mask from GC bitmap. // 1. nil - obtain pointer mask from GC bitmap.
// 2. pointer to a compact mask (for stacks and data). // 2. pointer to a compact mask (for stacks and data).
if wbuf == nil { scanobject(b, n, ptrmask, gcw)
wbuf = getpartialorempty(460) // no wbuf passed in.
}
wbuf = scanobject(b, n, ptrmask, wbuf)
if gcphase == _GCscan { if gcphase == _GCscan {
if inheap(b) && ptrmask == nil { if inheap(b) && ptrmask == nil {
// b is in heap, we are in GCscan so there should be a ptrmask. // b is in heap, we are in GCscan so there should be a ptrmask.
throw("scanblock: In GCscan phase and inheap is true.") throw("scanblock: In GCscan phase and inheap is true.")
} }
} }
return wbuf
} }
// gcDrain scans objects in work buffers (starting with wbuf), blackening grey // gcDrain scans objects in work buffers, blackening grey
// objects until all work buffers have been drained. // objects until all work has been drained.
//go:nowritebarrier //go:nowritebarrier
func gcDrain(wbuf *workbuf) { func gcDrain(gcw *gcWork) {
if wbuf == nil {
wbuf = getpartialorempty(472)
}
checknocurrentwbuf()
if gcphase != _GCmark && gcphase != _GCmarktermination { if gcphase != _GCmark && gcphase != _GCmarktermination {
throw("scanblock phase incorrect") throw("scanblock phase incorrect")
} }
for { for {
if wbuf.nobj == 0 {
putempty(wbuf, 496)
// Refill workbuf from global queue.
wbuf = getfull(504)
if wbuf == nil { // nil means out of work barrier reached
break
}
wbuf.checknonempty()
}
// If another proc wants a pointer, give it some. // If another proc wants a pointer, give it some.
if work.nwait > 0 && wbuf.nobj > 4 && work.full == 0 { if work.nwait > 0 && work.full == 0 {
wbuf = handoff(wbuf) gcw.balance()
} }
// This might be a good place to add prefetch code... b := gcw.get()
// if(wbuf.nobj > 4) { if b == 0 {
// PREFETCH(wbuf->obj[wbuf.nobj - 3]; // work barrier reached
// } break
wbuf.nobj-- }
b := wbuf.obj[wbuf.nobj]
// If the current wbuf is filled by the scan a new wbuf might be // If the current wbuf is filled by the scan a new wbuf might be
// returned that could possibly hold only a single object. This // returned that could possibly hold only a single object. This
// could result in each iteration draining only a single object // could result in each iteration draining only a single object
// out of the wbuf passed in + a single object placed // out of the wbuf passed in + a single object placed
// into an empty wbuf in scanobject so there could be // into an empty wbuf in scanobject so there could be
// a performance hit as we keep fetching fresh wbufs. // a performance hit as we keep fetching fresh wbufs.
wbuf = scanobject(b, 0, nil, wbuf) scanobject(b, 0, nil, &gcw.gcWorkProducer)
} }
checknocurrentwbuf() checknocurrentwbuf()
} }
// gcDrainN scans n objects starting with those in wbuf, blackening // gcDrainN scans n objects, blackening grey objects.
// grey objects.
//go:nowritebarrier //go:nowritebarrier
func gcDrainN(wbuf *workbuf, n int) *workbuf { func gcDrainN(gcw *gcWork, n int) {
checknocurrentwbuf() checknocurrentwbuf()
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
if wbuf.nobj == 0 {
putempty(wbuf, 544)
wbuf = trygetfull(545)
if wbuf == nil {
return nil
}
}
// This might be a good place to add prefetch code... // This might be a good place to add prefetch code...
// if(wbuf.nobj > 4) { // if(wbuf.nobj > 4) {
// PREFETCH(wbuf->obj[wbuf.nobj - 3]; // PREFETCH(wbuf->obj[wbuf.nobj - 3];
// } // }
wbuf.nobj-- b := gcw.tryGet()
b := wbuf.obj[wbuf.nobj] if b == 0 {
wbuf = scanobject(b, 0, nil, wbuf) return
}
scanobject(b, 0, nil, &gcw.gcWorkProducer)
} }
return wbuf
} }
//go:nowritebarrier //go:nowritebarrier
func markroot(desc *parfor, i uint32) { func markroot(desc *parfor, i uint32) {
var gcw gcWorkProducer
gcw.initFromCache()
// Note: if you add a case here, please also update heapdump.c:dumproots. // Note: if you add a case here, please also update heapdump.c:dumproots.
wbuf := (*workbuf)(unsafe.Pointer(xchguintptr(&getg().m.currentwbuf, 0)))
switch i { switch i {
case _RootData: case _RootData:
wbuf = scanblock(uintptr(unsafe.Pointer(&data)), uintptr(unsafe.Pointer(&edata))-uintptr(unsafe.Pointer(&data)), gcdatamask.bytedata, wbuf) scanblock(uintptr(unsafe.Pointer(&data)), uintptr(unsafe.Pointer(&edata))-uintptr(unsafe.Pointer(&data)), gcdatamask.bytedata, &gcw)
case _RootBss: case _RootBss:
wbuf = scanblock(uintptr(unsafe.Pointer(&bss)), uintptr(unsafe.Pointer(&ebss))-uintptr(unsafe.Pointer(&bss)), gcbssmask.bytedata, wbuf) scanblock(uintptr(unsafe.Pointer(&bss)), uintptr(unsafe.Pointer(&ebss))-uintptr(unsafe.Pointer(&bss)), gcbssmask.bytedata, &gcw)
case _RootFinalizers: case _RootFinalizers:
for fb := allfin; fb != nil; fb = fb.alllink { for fb := allfin; fb != nil; fb = fb.alllink {
wbuf = scanblock(uintptr(unsafe.Pointer(&fb.fin[0])), uintptr(fb.cnt)*unsafe.Sizeof(fb.fin[0]), &finptrmask[0], wbuf) scanblock(uintptr(unsafe.Pointer(&fb.fin[0])), uintptr(fb.cnt)*unsafe.Sizeof(fb.fin[0]), &finptrmask[0], &gcw)
} }
case _RootSpans: case _RootSpans:
@ -555,9 +521,9 @@ func markroot(desc *parfor, i uint32) {
// A finalizer can be set for an inner byte of an object, find object beginning. // A finalizer can be set for an inner byte of an object, find object beginning.
p := uintptr(s.start<<_PageShift) + uintptr(spf.special.offset)/s.elemsize*s.elemsize p := uintptr(s.start<<_PageShift) + uintptr(spf.special.offset)/s.elemsize*s.elemsize
if gcphase != _GCscan { if gcphase != _GCscan {
wbuf = scanblock(p, s.elemsize, nil, wbuf) // scanned during mark phase scanblock(p, s.elemsize, nil, &gcw) // scanned during mark phase
} }
wbuf = scanblock(uintptr(unsafe.Pointer(&spf.fn)), ptrSize, &oneptr[0], wbuf) scanblock(uintptr(unsafe.Pointer(&spf.fn)), ptrSize, &oneptr[0], &gcw)
} }
} }
@ -617,11 +583,7 @@ func markroot(desc *parfor, i uint32) {
restartg(gp) restartg(gp)
} }
} }
if wbuf == nil { gcw.dispose()
return
} else {
putpartial(wbuf, 670)
}
} }
//go:nowritebarrier //go:nowritebarrier
@ -634,13 +596,13 @@ func stackmapdata(stkmap *stackmap, n int32) bitvector {
// Scan a stack frame: local variables and function arguments/results. // Scan a stack frame: local variables and function arguments/results.
//go:nowritebarrier //go:nowritebarrier
func scanframeworker(frame *stkframe, unused unsafe.Pointer, wbuf *workbuf) *workbuf { func scanframeworker(frame *stkframe, unused unsafe.Pointer, gcw *gcWorkProducer) {
f := frame.fn f := frame.fn
targetpc := frame.continpc targetpc := frame.continpc
if targetpc == 0 { if targetpc == 0 {
// Frame is dead. // Frame is dead.
return wbuf return
} }
if _DebugGC > 1 { if _DebugGC > 1 {
print("scanframe ", funcname(f), "\n") print("scanframe ", funcname(f), "\n")
@ -679,7 +641,7 @@ func scanframeworker(frame *stkframe, unused unsafe.Pointer, wbuf *workbuf) *wor
} }
bv := stackmapdata(stkmap, pcdata) bv := stackmapdata(stkmap, pcdata)
size = (uintptr(bv.n) / typeBitsWidth) * ptrSize size = (uintptr(bv.n) / typeBitsWidth) * ptrSize
wbuf = scanblock(frame.varp-size, size, bv.bytedata, wbuf) scanblock(frame.varp-size, size, bv.bytedata, gcw)
} }
// Scan arguments. // Scan arguments.
@ -700,9 +662,8 @@ func scanframeworker(frame *stkframe, unused unsafe.Pointer, wbuf *workbuf) *wor
} }
bv = stackmapdata(stkmap, pcdata) bv = stackmapdata(stkmap, pcdata)
} }
wbuf = scanblock(frame.argp, uintptr(bv.n)/typeBitsWidth*ptrSize, bv.bytedata, wbuf) scanblock(frame.argp, uintptr(bv.n)/typeBitsWidth*ptrSize, bv.bytedata, gcw)
} }
return wbuf
} }
//go:nowritebarrier //go:nowritebarrier
@ -737,19 +698,17 @@ func scanstack(gp *g) {
throw("can't scan gchelper stack") throw("can't scan gchelper stack")
} }
wbuf := (*workbuf)(unsafe.Pointer(xchguintptr(&getg().m.currentwbuf, 0))) var gcw gcWorkProducer
gcw.initFromCache()
scanframe := func(frame *stkframe, unused unsafe.Pointer) bool { scanframe := func(frame *stkframe, unused unsafe.Pointer) bool {
// Pick up wbuf as free variable so gentraceback and friends can // Pick up gcw as free variable so gentraceback and friends can
// keep the same signature. // keep the same signature.
wbuf = scanframeworker(frame, unused, wbuf) scanframeworker(frame, unused, &gcw)
return true return true
} }
gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, scanframe, nil, 0) gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, scanframe, nil, 0)
tracebackdefers(gp, scanframe, nil) tracebackdefers(gp, scanframe, nil)
wbuf = (*workbuf)(unsafe.Pointer(xchguintptr(&getg().m.currentwbuf, uintptr(unsafe.Pointer(wbuf))))) gcw.disposeToCache()
if wbuf != nil {
throw("wbuf not nil after stack scans")
}
gp.gcscanvalid = true gp.gcscanvalid = true
} }
@ -757,8 +716,6 @@ func scanstack(gp *g) {
// The object is not nil and known to be in the heap. // The object is not nil and known to be in the heap.
//go:nowritebarrier //go:nowritebarrier
func shade(b uintptr) { func shade(b uintptr) {
var wbuf *workbuf
if !inheap(b) { if !inheap(b) {
throw("shade: passed an address not in the heap") throw("shade: passed an address not in the heap")
} }
@ -770,19 +727,23 @@ func shade(b uintptr) {
// if atomicload(&harvestingwbufs) == uint32(1) { // if atomicload(&harvestingwbufs) == uint32(1) {
// // Throw here to discover write barriers // // Throw here to discover write barriers
// // being executed during a STW. // // being executed during a STW.
// throw("shade during harvest")
// } // }
wbuf = getpartialorempty(1181) var gcw gcWorkProducer
wbuf := greyobject(obj, 0, 0, hbits, wbuf) greyobject(obj, 0, 0, hbits, &gcw)
checknocurrentwbuf()
// This is part of the write barrier so put the wbuf back. // This is part of the write barrier so put the wbuf back.
if gcphase == _GCmarktermination { if gcphase == _GCmarktermination {
putpartial(wbuf, 1191) // Put on full??? gcw.dispose()
} else { } else {
wbuf = (*workbuf)(unsafe.Pointer(xchguintptr(&getg().m.currentwbuf, uintptr(unsafe.Pointer(wbuf))))) // If we added any pointers to the gcw, then
if wbuf != nil { // currentwbuf must be nil because 1)
throw("m.currentwbuf lost in shade") // greyobject got its wbuf from currentwbuf
} // and 2) shade runs on the systemstack, so
// we're still on the same M. If either of
// these becomes no longer true, we need to
// rethink this.
gcw.disposeToCache()
} }
} }
} }
@ -807,26 +768,13 @@ func gchelpwork() {
// scanstack(gp) // scanstack(gp)
case _GCmark: case _GCmark:
// Get a full work buffer and empty it. // Get a full work buffer and empty it.
m := getg().m
// drain your own currentwbuf first in the hopes that it will // drain your own currentwbuf first in the hopes that it will
// be more cache friendly. // be more cache friendly.
wbuf := (*workbuf)(unsafe.Pointer(xchguintptr(&m.currentwbuf, 0))) var gcw gcWork
// wbuf := (*workbuf)(unsafe.Pointer(m.currentwbuf)) gcw.initFromCache()
// m.currentwbuf = 0 const n = len(workbuf{}.obj)
if wbuf == nil { gcDrainN(&gcw, n) // drain upto one buffer's worth of objects
wbuf = trygetfull(1228) gcw.dispose()
}
if wbuf != nil {
const n = len(workbuf{}.obj)
wbuf = gcDrainN(wbuf, n) // drain upto one buffer's worth of objects
if wbuf != nil {
if wbuf.nobj != 0 {
putfull(wbuf, 1175)
} else {
putempty(wbuf, 1177)
}
}
}
case _GCmarktermination: case _GCmarktermination:
// We should never be here since the world is stopped. // We should never be here since the world is stopped.
// All available mark work will be emptied before returning. // All available mark work will be emptied before returning.
@ -1140,7 +1088,9 @@ func gchelper() {
// parallel mark for over GC roots // parallel mark for over GC roots
parfordo(work.markfor) parfordo(work.markfor)
if gcphase != _GCscan { if gcphase != _GCscan {
gcDrain(nil) // blocks in getfull var gcw gcWork
gcDrain(&gcw) // blocks in getfull
gcw.dispose()
} }
if trace.enabled { if trace.enabled {
@ -1402,7 +1352,9 @@ func gcscan_m() {
// This is the concurrent mark phase. // This is the concurrent mark phase.
//go:nowritebarrier //go:nowritebarrier
func gcmark_m() { func gcmark_m() {
gcDrain(nil) var gcw gcWork
gcDrain(&gcw)
gcw.dispose()
// TODO add another harvestwbuf and reset work.nwait=0, work.ndone=0, and work.nproc=1 // TODO add another harvestwbuf and reset work.nwait=0, work.ndone=0, and work.nproc=1
// and repeat the above gcDrain. // and repeat the above gcDrain.
} }
@ -1489,7 +1441,9 @@ func gc(start_time int64, eagersweep bool) {
harvestwbufs() // move local workbufs onto global queues where the GC can find them harvestwbufs() // move local workbufs onto global queues where the GC can find them
gchelperstart() gchelperstart()
parfordo(work.markfor) parfordo(work.markfor)
gcDrain(nil) var gcw gcWork
gcDrain(&gcw)
gcw.dispose()
if work.full != 0 { if work.full != 0 {
throw("work.full != 0") throw("work.full != 0")