runtime (gc_blocks.go): use a linked stack to scan marked objects

The blocks GC originally used a fixed-size stack to hold objects to scan.
When this stack overflowed, the GC would fully rescan all marked objects.
This could cause the GC to degrade to O(n^2) when scanning large linked data structures.

Instead of using a fixed-size stack, we now add a pointer field to the start of each object.
This pointer field is used to implement an unbounded linked stack.
This also consolidates the heap object scanning into one place, which simplifies the process.

This comes at the cost of introducing a pointer field to the start of the object, plus the cost of aligning the result.
This translates to:
- 16 bytes of overhead on x86/arm64 with the conservative collector
- 0 bytes of overhead on x86/arm64 with the precise collector (the layout field cost gets aligned up to 16 bytes anyway)
- 8 bytes of overhead on other 64-bit systems
- 4 bytes of overhead on 32-bit systems
- 2 bytes of overhead on AVR
This commit is contained in:
Nia Waldvogel
2025-11-29 19:58:03 -05:00
committed by Ron Evans
parent 887ac286ff
commit c9aa88b8ef
4 changed files with 106 additions and 104 deletions
+19 -12
View File
@@ -59,19 +59,19 @@ import "unsafe"
const preciseHeap = true
type gcObjectScanner struct {
index uintptr
size uintptr
bitmap uintptr
bitmapAddr unsafe.Pointer
// parseGCLayout stores the layout information passed to alloc into a gcLayout value.
func parseGCLayout(layout unsafe.Pointer) gcLayout {
return gcLayout{layout: uintptr(layout)}
}
func newGCObjectScanner(block gcBlock) gcObjectScanner {
if gcAsserts && block != block.findHead() {
runtimePanic("gc: object scanner must start at head")
}
scanner := gcObjectScanner{}
layout := *(*uintptr)(unsafe.Pointer(block.address()))
// gcLayout tracks pointer locations in a heap object.
type gcLayout struct {
layout uintptr
}
// scanner creates a gcObjectScanner with this layout.
func (l gcLayout) scanner() (scanner gcObjectScanner) {
layout := l.layout
if layout == 0 {
// Unknown layout. Assume all words in the object could be pointers.
// This layout value below corresponds to a slice of pointers like:
@@ -104,7 +104,14 @@ func newGCObjectScanner(block gcBlock) gcObjectScanner {
scanner.size = *(*uintptr)(layoutAddr)
scanner.bitmapAddr = unsafe.Add(layoutAddr, unsafe.Sizeof(uintptr(0)))
}
return scanner
return
}
type gcObjectScanner struct {
index uintptr
size uintptr
bitmap uintptr
bitmapAddr unsafe.Pointer
}
func (scanner *gcObjectScanner) pointerFree() bool {