github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/runtime/mgcwork.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package runtime 6 7 import ( 8 "runtime/internal/atomic" 9 "runtime/internal/sys" 10 "unsafe" 11 ) 12 13 const ( 14 _WorkbufSize = 2048 // in bytes; larger values result in less contention 15 16 // workbufAlloc is the number of bytes to allocate at a time 17 // for new workbufs. This must be a multiple of pageSize and 18 // should be a multiple of _WorkbufSize. 19 // 20 // Larger values reduce workbuf allocation overhead. Smaller 21 // values reduce heap fragmentation. 22 workbufAlloc = 32 << 10 23 ) 24 25 func init() { 26 if workbufAlloc%pageSize != 0 || workbufAlloc%_WorkbufSize != 0 { 27 throw("bad workbufAlloc") 28 } 29 } 30 31 // Garbage collector work pool abstraction. 32 // 33 // This implements a producer/consumer model for pointers to grey 34 // objects. A grey object is one that is marked and on a work 35 // queue. A black object is marked and not on a work queue. 36 // 37 // Write barriers, root discovery, stack scanning, and object scanning 38 // produce pointers to grey objects. Scanning consumes pointers to 39 // grey objects, thus blackening them, and then scans them, 40 // potentially producing new pointers to grey objects. 41 42 // A gcWork provides the interface to produce and consume work for the 43 // garbage collector. 44 // 45 // A gcWork can be used on the stack as follows: 46 // 47 // (preemption must be disabled) 48 // gcw := &getg().m.p.ptr().gcw 49 // .. call gcw.put() to produce and gcw.tryGet() to consume .. 50 // 51 // It's important that any use of gcWork during the mark phase prevent 52 // the garbage collector from transitioning to mark termination since 53 // gcWork may locally hold GC work buffers. This can be done by 54 // disabling preemption (systemstack or acquirem). 55 type gcWork struct { 56 // wbuf1 and wbuf2 are the primary and secondary work buffers. 57 // 58 // This can be thought of as a stack of both work buffers' 59 // pointers concatenated. When we pop the last pointer, we 60 // shift the stack up by one work buffer by bringing in a new 61 // full buffer and discarding an empty one. When we fill both 62 // buffers, we shift the stack down by one work buffer by 63 // bringing in a new empty buffer and discarding a full one. 64 // This way we have one buffer's worth of hysteresis, which 65 // amortizes the cost of getting or putting a work buffer over 66 // at least one buffer of work and reduces contention on the 67 // global work lists. 68 // 69 // wbuf1 is always the buffer we're currently pushing to and 70 // popping from and wbuf2 is the buffer that will be discarded 71 // next. 72 // 73 // Invariant: Both wbuf1 and wbuf2 are nil or neither are. 74 wbuf1, wbuf2 *workbuf 75 76 // Bytes marked (blackened) on this gcWork. This is aggregated 77 // into work.bytesMarked by dispose. 78 bytesMarked uint64 79 80 // Scan work performed on this gcWork. This is aggregated into 81 // gcController by dispose and may also be flushed by callers. 82 scanWork int64 83 84 // flushedWork indicates that a non-empty work buffer was 85 // flushed to the global work list since the last gcMarkDone 86 // termination check. Specifically, this indicates that this 87 // gcWork may have communicated work to another gcWork. 88 flushedWork bool 89 } 90 91 // Most of the methods of gcWork are go:nowritebarrierrec because the 92 // write barrier itself can invoke gcWork methods but the methods are 93 // not generally re-entrant. Hence, if a gcWork method invoked the 94 // write barrier while the gcWork was in an inconsistent state, and 95 // the write barrier in turn invoked a gcWork method, it could 96 // permanently corrupt the gcWork. 97 98 func (w *gcWork) init() { 99 w.wbuf1 = getempty() 100 wbuf2 := trygetfull() 101 if wbuf2 == nil { 102 wbuf2 = getempty() 103 } 104 w.wbuf2 = wbuf2 105 } 106 107 // put enqueues a pointer for the garbage collector to trace. 108 // obj must point to the beginning of a heap object or an oblet. 109 //go:nowritebarrierrec 110 func (w *gcWork) put(obj uintptr) { 111 flushed := false 112 wbuf := w.wbuf1 113 // Record that this may acquire the wbufSpans or heap lock to 114 // allocate a workbuf. 115 lockWithRankMayAcquire(&work.wbufSpans.lock, lockRankWbufSpans) 116 lockWithRankMayAcquire(&mheap_.lock, lockRankMheap) 117 if wbuf == nil { 118 w.init() 119 wbuf = w.wbuf1 120 // wbuf is empty at this point. 121 } else if wbuf.nobj == len(wbuf.obj) { 122 w.wbuf1, w.wbuf2 = w.wbuf2, w.wbuf1 123 wbuf = w.wbuf1 124 if wbuf.nobj == len(wbuf.obj) { 125 putfull(wbuf) 126 w.flushedWork = true 127 wbuf = getempty() 128 w.wbuf1 = wbuf 129 flushed = true 130 } 131 } 132 133 wbuf.obj[wbuf.nobj] = obj 134 wbuf.nobj++ 135 136 // If we put a buffer on full, let the GC controller know so 137 // it can encourage more workers to run. We delay this until 138 // the end of put so that w is in a consistent state, since 139 // enlistWorker may itself manipulate w. 140 if flushed && gcphase == _GCmark { 141 gcController.enlistWorker() 142 } 143 } 144 145 // putFast does a put and reports whether it can be done quickly 146 // otherwise it returns false and the caller needs to call put. 147 //go:nowritebarrierrec 148 func (w *gcWork) putFast(obj uintptr) bool { 149 wbuf := w.wbuf1 150 if wbuf == nil { 151 return false 152 } else if wbuf.nobj == len(wbuf.obj) { 153 return false 154 } 155 156 wbuf.obj[wbuf.nobj] = obj 157 wbuf.nobj++ 158 return true 159 } 160 161 // putBatch performs a put on every pointer in obj. See put for 162 // constraints on these pointers. 163 // 164 //go:nowritebarrierrec 165 func (w *gcWork) putBatch(obj []uintptr) { 166 if len(obj) == 0 { 167 return 168 } 169 170 flushed := false 171 wbuf := w.wbuf1 172 if wbuf == nil { 173 w.init() 174 wbuf = w.wbuf1 175 } 176 177 for len(obj) > 0 { 178 for wbuf.nobj == len(wbuf.obj) { 179 putfull(wbuf) 180 w.flushedWork = true 181 w.wbuf1, w.wbuf2 = w.wbuf2, getempty() 182 wbuf = w.wbuf1 183 flushed = true 184 } 185 n := copy(wbuf.obj[wbuf.nobj:], obj) 186 wbuf.nobj += n 187 obj = obj[n:] 188 } 189 190 if flushed && gcphase == _GCmark { 191 gcController.enlistWorker() 192 } 193 } 194 195 // tryGet dequeues a pointer for the garbage collector to trace. 196 // 197 // If there are no pointers remaining in this gcWork or in the global 198 // queue, tryGet returns 0. Note that there may still be pointers in 199 // other gcWork instances or other caches. 200 //go:nowritebarrierrec 201 func (w *gcWork) tryGet() uintptr { 202 wbuf := w.wbuf1 203 if wbuf == nil { 204 w.init() 205 wbuf = w.wbuf1 206 // wbuf is empty at this point. 207 } 208 if wbuf.nobj == 0 { 209 w.wbuf1, w.wbuf2 = w.wbuf2, w.wbuf1 210 wbuf = w.wbuf1 211 if wbuf.nobj == 0 { 212 owbuf := wbuf 213 wbuf = trygetfull() 214 if wbuf == nil { 215 return 0 216 } 217 putempty(owbuf) 218 w.wbuf1 = wbuf 219 } 220 } 221 222 wbuf.nobj-- 223 return wbuf.obj[wbuf.nobj] 224 } 225 226 // tryGetFast dequeues a pointer for the garbage collector to trace 227 // if one is readily available. Otherwise it returns 0 and 228 // the caller is expected to call tryGet(). 229 //go:nowritebarrierrec 230 func (w *gcWork) tryGetFast() uintptr { 231 wbuf := w.wbuf1 232 if wbuf == nil { 233 return 0 234 } 235 if wbuf.nobj == 0 { 236 return 0 237 } 238 239 wbuf.nobj-- 240 return wbuf.obj[wbuf.nobj] 241 } 242 243 // dispose returns any cached pointers to the global queue. 244 // The buffers are being put on the full queue so that the 245 // write barriers will not simply reacquire them before the 246 // GC can inspect them. This helps reduce the mutator's 247 // ability to hide pointers during the concurrent mark phase. 248 // 249 //go:nowritebarrierrec 250 func (w *gcWork) dispose() { 251 if wbuf := w.wbuf1; wbuf != nil { 252 if wbuf.nobj == 0 { 253 putempty(wbuf) 254 } else { 255 putfull(wbuf) 256 w.flushedWork = true 257 } 258 w.wbuf1 = nil 259 260 wbuf = w.wbuf2 261 if wbuf.nobj == 0 { 262 putempty(wbuf) 263 } else { 264 putfull(wbuf) 265 w.flushedWork = true 266 } 267 w.wbuf2 = nil 268 } 269 if w.bytesMarked != 0 { 270 // dispose happens relatively infrequently. If this 271 // atomic becomes a problem, we should first try to 272 // dispose less and if necessary aggregate in a per-P 273 // counter. 274 atomic.Xadd64(&work.bytesMarked, int64(w.bytesMarked)) 275 w.bytesMarked = 0 276 } 277 if w.scanWork != 0 { 278 atomic.Xaddint64(&gcController.scanWork, w.scanWork) 279 w.scanWork = 0 280 } 281 } 282 283 // balance moves some work that's cached in this gcWork back on the 284 // global queue. 285 //go:nowritebarrierrec 286 func (w *gcWork) balance() { 287 if w.wbuf1 == nil { 288 return 289 } 290 if wbuf := w.wbuf2; wbuf.nobj != 0 { 291 putfull(wbuf) 292 w.flushedWork = true 293 w.wbuf2 = getempty() 294 } else if wbuf := w.wbuf1; wbuf.nobj > 4 { 295 w.wbuf1 = handoff(wbuf) 296 w.flushedWork = true // handoff did putfull 297 } else { 298 return 299 } 300 // We flushed a buffer to the full list, so wake a worker. 301 if gcphase == _GCmark { 302 gcController.enlistWorker() 303 } 304 } 305 306 // empty reports whether w has no mark work available. 307 //go:nowritebarrierrec 308 func (w *gcWork) empty() bool { 309 return w.wbuf1 == nil || (w.wbuf1.nobj == 0 && w.wbuf2.nobj == 0) 310 } 311 312 // Internally, the GC work pool is kept in arrays in work buffers. 313 // The gcWork interface caches a work buffer until full (or empty) to 314 // avoid contending on the global work buffer lists. 315 316 type workbufhdr struct { 317 node lfnode // must be first 318 nobj int 319 } 320 321 //go:notinheap 322 type workbuf struct { 323 workbufhdr 324 // account for the above fields 325 obj [(_WorkbufSize - unsafe.Sizeof(workbufhdr{})) / sys.PtrSize]uintptr 326 } 327 328 // workbuf factory routines. These funcs are used to manage the 329 // workbufs. 330 // If the GC asks for some work these are the only routines that 331 // make wbufs available to the GC. 332 333 func (b *workbuf) checknonempty() { 334 if b.nobj == 0 { 335 throw("workbuf is empty") 336 } 337 } 338 339 func (b *workbuf) checkempty() { 340 if b.nobj != 0 { 341 throw("workbuf is not empty") 342 } 343 } 344 345 // getempty pops an empty work buffer off the work.empty list, 346 // allocating new buffers if none are available. 347 //go:nowritebarrier 348 func getempty() *workbuf { 349 var b *workbuf 350 if work.empty != 0 { 351 b = (*workbuf)(work.empty.pop()) 352 if b != nil { 353 b.checkempty() 354 } 355 } 356 // Record that this may acquire the wbufSpans or heap lock to 357 // allocate a workbuf. 358 lockWithRankMayAcquire(&work.wbufSpans.lock, lockRankWbufSpans) 359 lockWithRankMayAcquire(&mheap_.lock, lockRankMheap) 360 if b == nil { 361 // Allocate more workbufs. 362 var s *mspan 363 if work.wbufSpans.free.first != nil { 364 lock(&work.wbufSpans.lock) 365 s = work.wbufSpans.free.first 366 if s != nil { 367 work.wbufSpans.free.remove(s) 368 work.wbufSpans.busy.insert(s) 369 } 370 unlock(&work.wbufSpans.lock) 371 } 372 if s == nil { 373 systemstack(func() { 374 s = mheap_.allocManual(workbufAlloc/pageSize, spanAllocWorkBuf) 375 }) 376 if s == nil { 377 throw("out of memory") 378 } 379 // Record the new span in the busy list. 380 lock(&work.wbufSpans.lock) 381 work.wbufSpans.busy.insert(s) 382 unlock(&work.wbufSpans.lock) 383 } 384 // Slice up the span into new workbufs. Return one and 385 // put the rest on the empty list. 386 for i := uintptr(0); i+_WorkbufSize <= workbufAlloc; i += _WorkbufSize { 387 newb := (*workbuf)(unsafe.Pointer(s.base() + i)) 388 newb.nobj = 0 389 lfnodeValidate(&newb.node) 390 if i == 0 { 391 b = newb 392 } else { 393 putempty(newb) 394 } 395 } 396 } 397 return b 398 } 399 400 // putempty puts a workbuf onto the work.empty list. 401 // Upon entry this go routine owns b. The lfstack.push relinquishes ownership. 402 //go:nowritebarrier 403 func putempty(b *workbuf) { 404 b.checkempty() 405 work.empty.push(&b.node) 406 } 407 408 // putfull puts the workbuf on the work.full list for the GC. 409 // putfull accepts partially full buffers so the GC can avoid competing 410 // with the mutators for ownership of partially full buffers. 411 //go:nowritebarrier 412 func putfull(b *workbuf) { 413 b.checknonempty() 414 work.full.push(&b.node) 415 } 416 417 // trygetfull tries to get a full or partially empty workbuffer. 418 // If one is not immediately available return nil 419 //go:nowritebarrier 420 func trygetfull() *workbuf { 421 b := (*workbuf)(work.full.pop()) 422 if b != nil { 423 b.checknonempty() 424 return b 425 } 426 return b 427 } 428 429 //go:nowritebarrier 430 func handoff(b *workbuf) *workbuf { 431 // Make new buffer with half of b's pointers. 432 b1 := getempty() 433 n := b.nobj / 2 434 b.nobj -= n 435 b1.nobj = n 436 memmove(unsafe.Pointer(&b1.obj[0]), unsafe.Pointer(&b.obj[b.nobj]), uintptr(n)*unsafe.Sizeof(b1.obj[0])) 437 438 // Put b on full list - let first half of b get stolen. 439 putfull(b) 440 return b1 441 } 442 443 // prepareFreeWorkbufs moves busy workbuf spans to free list so they 444 // can be freed to the heap. This must only be called when all 445 // workbufs are on the empty list. 446 func prepareFreeWorkbufs() { 447 lock(&work.wbufSpans.lock) 448 if work.full != 0 { 449 throw("cannot free workbufs when work.full != 0") 450 } 451 // Since all workbufs are on the empty list, we don't care 452 // which ones are in which spans. We can wipe the entire empty 453 // list and move all workbuf spans to the free list. 454 work.empty = 0 455 work.wbufSpans.free.takeAll(&work.wbufSpans.busy) 456 unlock(&work.wbufSpans.lock) 457 } 458 459 // freeSomeWbufs frees some workbufs back to the heap and returns 460 // true if it should be called again to free more. 461 func freeSomeWbufs(preemptible bool) bool { 462 const batchSize = 64 // ~1–2 µs per span. 463 lock(&work.wbufSpans.lock) 464 if gcphase != _GCoff || work.wbufSpans.free.isEmpty() { 465 unlock(&work.wbufSpans.lock) 466 return false 467 } 468 systemstack(func() { 469 gp := getg().m.curg 470 for i := 0; i < batchSize && !(preemptible && gp.preempt); i++ { 471 span := work.wbufSpans.free.first 472 if span == nil { 473 break 474 } 475 work.wbufSpans.free.remove(span) 476 mheap_.freeManual(span, spanAllocWorkBuf) 477 } 478 }) 479 more := !work.wbufSpans.free.isEmpty() 480 unlock(&work.wbufSpans.lock) 481 return more 482 }