diff options
author | Ayke van Laethem <[email protected]> | 2022-05-31 15:19:32 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2022-09-16 11:11:50 +0200 |
commit | 91104b2f276348e251a25e9e58e7faafe781358f (patch) | |
tree | 267a3bb7b16f7cadbeb17b9519b9d919d2b2ace5 | |
parent | 80f38e84496facc098eeb1a51e76a17c7459a6d6 (diff) | |
download | tinygo-91104b2f276348e251a25e9e58e7faafe781358f.tar.gz tinygo-91104b2f276348e251a25e9e58e7faafe781358f.zip |
runtime: ensure some headroom for the GC to run
The GC was originally designed for systems with a fixed amount of
memory, like baremetal systems. Therefore, it just used what it could
and ran a GC cycle when out of memory.
Other systems (like Linux or WebAssembly) are different. In those
systems, it is possible to grow the amount of memory on demand. But the
GC only actually grew the heap when it was really out of memory, not
when it was getting very close to being out of memory.
This patch fixes this by ensuring there is at least 33% headroom for the
GC. This means that programs can allocate around 50% more than what was
live in the last GC cycle. It should fix a performance cliff when a
program is almost, but not entirely, out of memory and the GC has to run
almost every heap allocation.
-rw-r--r-- | src/runtime/gc_conservative.go | 28 |
1 files changed, 25 insertions, 3 deletions
diff --git a/src/runtime/gc_conservative.go b/src/runtime/gc_conservative.go index 396e1a68d..7bc984ca4 100644 --- a/src/runtime/gc_conservative.go +++ b/src/runtime/gc_conservative.go @@ -288,7 +288,14 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { // could be found. Run a garbage collection cycle to reclaim // free memory and try again. heapScanCount = 2 - GC() + freeBytes := runGC() + heapSize := uintptr(metadataStart) - heapStart + if freeBytes < heapSize/3 { + // Ensure there is at least 33% headroom. + // This percentage was arbitrarily chosen, and may need to + // be tuned in the future. + growHeap() + } } else { // Even after garbage collection, no free memory could be found. // Try to increase heap size. @@ -379,6 +386,13 @@ func free(ptr unsafe.Pointer) { // GC performs a garbage collection cycle. func GC() { + runGC() +} + +// runGC performs a garbage colleciton cycle. It is the internal implementation +// of the runtime.GC() function. The difference is that it returns the number of +// free bytes in the heap after the GC is finished. +func runGC() (freeBytes uintptr) { if gcDebug { println("running collection cycle...") } @@ -420,12 +434,14 @@ func GC() { // Sweep phase: free all non-marked objects and unmark marked objects for // the next collection cycle. - sweep() + freeBytes = sweep() // Show how much has been sweeped, for debugging. if gcDebug { dumpHeap() } + + return } // markRoots reads all pointers from start to end (exclusive) and if they look @@ -568,7 +584,8 @@ func markRoot(addr, root uintptr) { } // Sweep goes through all memory and frees unmarked memory. -func sweep() { +// It returns how many bytes are free in the heap after the sweep. +func sweep() (freeBytes uintptr) { freeCurrentObject := false for block := gcBlock(0); block < endBlock; block++ { switch block.state() { @@ -577,11 +594,13 @@ func sweep() { block.markFree() freeCurrentObject = true gcFrees++ + freeBytes += bytesPerBlock case blockStateTail: if freeCurrentObject { // This is a tail object following an unmarked head. // Free it now. block.markFree() + freeBytes += bytesPerBlock } case blockStateMark: // This is a marked object. The next tail blocks must not be freed, @@ -589,8 +608,11 @@ func sweep() { // collect this object if it is unreferenced then. block.unmark() freeCurrentObject = false + case blockStateFree: + freeBytes += bytesPerBlock } } + return } // looksLikePointer returns whether this could be a pointer. Currently, it |