Skip to content

Commit 39e40af

Browse files
committed
runtime (gc): implement a best-fit allocator
1 parent 012c4a0 commit 39e40af

File tree

1 file changed

+167
-58
lines changed

1 file changed

+167
-58
lines changed

src/runtime/gc_conservative.go

Lines changed: 167 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ const (
4949

5050
var (
5151
poolStart uintptr // the first heap pointer
52-
nextAlloc gcBlock // the next block that should be tried by the allocator
5352
endBlock gcBlock // the block just past the end of the available space
5453
)
5554

@@ -207,6 +206,105 @@ func initHeap() {
207206

208207
// Set all block states to 'free'.
209208
memzero(unsafe.Pointer(heapStart), metadataSize)
209+
210+
// Initialize the freeList with a single node encompassing the entire heap.
211+
freeList = endBlock
212+
freeListInsert(0, numBlocks)
213+
}
214+
215+
var freeList gcBlock
216+
217+
type freeListNode struct {
218+
prev, next *freeListNode
219+
size uintptr
220+
}
221+
222+
// freeListHead returns a pointer to the first entry in the free list.
223+
// If the free list is empty, this returns nil.
224+
func freeListHead() *freeListNode {
225+
if freeList == endBlock {
226+
return nil
227+
}
228+
229+
return (*freeListNode)(unsafe.Pointer(freeList.pointer()))
230+
}
231+
232+
// freeListFind searches the free list for the smallest gap that is at least the specified size (in blocks).
233+
func freeListFind(size uintptr) gcBlock {
234+
for node := freeListHead(); node != nil; node = node.next {
235+
if node.size >= size {
236+
return blockFromAddr(uintptr(unsafe.Pointer(node)))
237+
}
238+
}
239+
240+
// The entire free list was scanned, and no blocks were sufficiently large.
241+
return endBlock
242+
}
243+
244+
// freeListInsert adds a free region starting at block, with the specified size (in blocks).
245+
func freeListInsert(block gcBlock, size uintptr) {
246+
// Search for an insertion point.
247+
var prev *freeListNode
248+
next := freeListHead()
249+
for ; next != nil && next.size < size; next = next.next {
250+
prev = next
251+
}
252+
253+
// Build the new free list entry.
254+
node := (*freeListNode)(block.pointer())
255+
*node = freeListNode{
256+
prev: prev,
257+
next: next,
258+
size: size,
259+
}
260+
261+
if prev != nil {
262+
// Update previous entry to reference this.
263+
prev.next = node
264+
} else {
265+
// Update list head.
266+
freeList = block
267+
}
268+
269+
if next != nil {
270+
// Update next entry to reference this.
271+
next.prev = node
272+
}
273+
}
274+
275+
// freeListRemove removes the gap starting at the specified block from the free list.
276+
// It returns the size of the gap that was removed.
277+
func freeListRemove(block gcBlock) uintptr {
278+
node := (*freeListNode)(block.pointer())
279+
prev, next := node.prev, node.next
280+
281+
if prev != nil {
282+
// Update previous node.
283+
prev.next = next
284+
} else {
285+
// Update list head.
286+
if next == nil {
287+
// List is now empty.
288+
freeList = endBlock
289+
} else {
290+
// The former next node becomes the head.
291+
freeList = blockFromAddr(uintptr(unsafe.Pointer(next)))
292+
}
293+
}
294+
295+
if next != nil {
296+
// Update next node.
297+
next.prev = prev
298+
}
299+
300+
return node.size
301+
}
302+
303+
func freeListDump() {
304+
println("free list:")
305+
for node := freeListHead(); node != nil; node = node.next {
306+
println("", node, "size:", node.size)
307+
}
210308
}
211309

212310
// alloc tries to find some free space on the heap, possibly doing a garbage
@@ -219,65 +317,50 @@ func alloc(size uintptr) unsafe.Pointer {
219317

220318
neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock
221319

222-
// Continue looping until a run of free blocks has been found that fits the
223-
// requested size.
224-
index := nextAlloc
225-
numFreeBlocks := uintptr(0)
226-
heapScanCount := uint8(0)
227-
for {
228-
if index == nextAlloc {
229-
if heapScanCount == 0 {
230-
heapScanCount = 1
231-
} else if heapScanCount == 1 {
232-
// The entire heap has been searched for free memory, but none
233-
// could be found. Run a garbage collection cycle to reclaim
234-
// free memory and try again.
235-
heapScanCount = 2
236-
GC()
237-
} else {
238-
// Even after garbage collection, no free memory could be found.
239-
runtimePanic("out of memory")
320+
// Find an unused gap in the heap to fill with the allocation.
321+
var ranGC bool
322+
findMem:
323+
block := freeListFind(neededBlocks)
324+
if block == endBlock {
325+
// There are no available gaps big enough for the allocation.
326+
if ranGC {
327+
// Even after garbage collection, no free memory could be found.
328+
if gcDebug {
329+
freeListDump()
330+
dumpHeap()
240331
}
332+
runtimePanic("out of memory")
241333
}
242334

243-
// Wrap around the end of the heap.
244-
if index == endBlock {
245-
index = 0
246-
// Reset numFreeBlocks as allocations cannot wrap.
247-
numFreeBlocks = 0
248-
}
335+
// Run the garbage collector and try again.
336+
GC()
337+
ranGC = true
338+
goto findMem
339+
}
249340

250-
// Is the block we're looking at free?
251-
if index.state() != blockStateFree {
252-
// This block is in use. Try again from this point.
253-
numFreeBlocks = 0
254-
index++
255-
continue
341+
// Update the free list.
342+
gapSize := freeListRemove(block)
343+
if gcDebug {
344+
println("using gap", block, "of size", gapSize)
345+
}
346+
if gapSize > neededBlocks {
347+
// Create a new free list entry with the leftover space.
348+
if gcDebug {
349+
println("creating shrunk gap", block+gcBlock(neededBlocks), "of size", gapSize-neededBlocks)
256350
}
257-
numFreeBlocks++
258-
index++
259-
260-
// Are we finished?
261-
if numFreeBlocks == neededBlocks {
262-
// Found a big enough range of free blocks!
263-
nextAlloc = index
264-
thisAlloc := index - gcBlock(neededBlocks)
265-
if gcDebug {
266-
println("found memory:", thisAlloc.pointer(), int(size))
267-
}
268-
269-
// Set the following blocks as being allocated.
270-
thisAlloc.setState(blockStateHead)
271-
for i := thisAlloc + 1; i != nextAlloc; i++ {
272-
i.setState(blockStateTail)
273-
}
351+
freeListInsert(block+gcBlock(neededBlocks), gapSize-neededBlocks)
352+
}
274353

275-
// Return a pointer to this allocation.
276-
pointer := thisAlloc.pointer()
277-
memzero(pointer, size)
278-
return pointer
279-
}
354+
// Update block metadata.
355+
block.setState(blockStateHead)
356+
for offset := uintptr(1); offset < neededBlocks; offset++ {
357+
(block + gcBlock(offset)).setState(blockStateTail)
280358
}
359+
360+
// Return a pointer to this allocation.
361+
pointer := block.pointer()
362+
memzero(pointer, neededBlocks*bytesPerBlock)
363+
return pointer
281364
}
282365

283366
func free(ptr unsafe.Pointer) {
@@ -348,15 +431,31 @@ func markRoot(addr, root uintptr) {
348431

349432
// Sweep goes through all memory and frees unmarked memory.
350433
func sweep() {
351-
freeCurrentObject := false
434+
// Clear free list. It will be rebuilt while sweeping.
435+
freeList = endBlock
436+
437+
// Sweep and rebuild free list.
438+
var freeStart gcBlock
439+
freeStarted := false
352440
for block := gcBlock(0); block < endBlock; block++ {
353-
switch block.state() {
441+
state := block.state()
442+
switch state {
443+
case blockStateFree:
444+
if !freeStarted {
445+
// Start a new free block.
446+
freeStart = block
447+
freeStarted = true
448+
}
354449
case blockStateHead:
355450
// Unmarked head. Free it, including all tail blocks following it.
356451
block.markFree()
357-
freeCurrentObject = true
452+
if !freeStarted {
453+
// Start a new free block with this head.
454+
freeStart = block
455+
freeStarted = true
456+
}
358457
case blockStateTail:
359-
if freeCurrentObject {
458+
if freeStarted {
360459
// This is a tail object following an unmarked head.
361460
// Free it now.
362461
block.markFree()
@@ -366,9 +465,19 @@ func sweep() {
366465
// but the mark bit must be removed so the next GC cycle will
367466
// collect this object if it is unreferenced then.
368467
block.unmark()
369-
freeCurrentObject = false
468+
469+
if freeStarted {
470+
// Add the previous gap to the free list.
471+
freeListInsert(freeStart, uintptr(block-freeStart))
472+
freeStarted = false
473+
}
370474
}
371475
}
476+
477+
if freeStarted {
478+
// Add the last gap to the free list.
479+
freeListInsert(freeStart, uintptr(endBlock-freeStart))
480+
}
372481
}
373482

374483
// looksLikePointer returns whether this could be a pointer. Currently, it

0 commit comments

Comments
 (0)