Skip to content

Commit 2c4d883

Browse files
committed
runtime (gc): switch to a best-fit allocator
1 parent 9a20af5 commit 2c4d883

File tree

1 file changed

+168
-57
lines changed

1 file changed

+168
-57
lines changed

src/runtime/gc_conservative.go

Lines changed: 168 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,18 @@ func (b gcBlock) address() uintptr {
110110
return poolStart + uintptr(b)*bytesPerBlock
111111
}
112112

113+
// setAlloc sets states of a series of gc blocks to list it as allocated.
114+
func (b gcBlock) setAlloc(len uintptr) {
115+
if len == 0 {
116+
return
117+
}
118+
119+
b.setState(blockStateHead)
120+
for i := uintptr(1); i < len; i++ {
121+
(b + gcBlock(i)).setState(blockStateTail)
122+
}
123+
}
124+
113125
// findHead returns the head (first block) of an object, assuming the block
114126
// points to an allocated object. It returns the same block if this block
115127
// already points to the head.
@@ -177,10 +189,133 @@ func (b gcBlock) unmark() {
177189
}
178190
}
179191

192+
// gcFreeSpan stores information about a span of free blocks.
193+
// It is stored in the first free block of the span.
194+
type gcFreeSpan struct {
195+
// next is a pointer to the next free span of equal size.
196+
next *gcFreeSpan
197+
}
198+
199+
// gcBlock returns the first block in the free span.
200+
func (span *gcFreeSpan) gcBlock() gcBlock {
201+
return blockFromAddr(uintptr(unsafe.Pointer(span)))
202+
}
203+
204+
// gcFreeRootSpan stores information about available free block spans.
205+
type gcFreeRootSpan struct {
206+
gcFreeSpan
207+
208+
// len is the length of the free span (measured in blocks).
209+
len uintptr
210+
211+
// nextSize is a pointer to the root span of the next smallest span size.
212+
nextSize *gcFreeRootSpan
213+
}
214+
215+
// gcFreeSpans is a tree which is used to track spans of free blocks in the heap.
216+
// This tree is asymmetric, and can also be viewed as a linked list of linked lists.
217+
// The outer list tracks available node sizes, and is sorted in ascending size order.
218+
// The inner lists are of individual free spans of a given size, sorted in no particular order.
219+
// This ensures that the worst case lookup time for the smallest span fitting an allocation is proportional to the size of the allocation.
220+
// However, the minimum heap size for n different sized spans is ½n² + (3/2)n - 2.
221+
// Therefore the worst case lookup complexity with respect to heap size is O(√(8n+17)) ≈ O(√n).
222+
var gcFreeSpans *gcFreeRootSpan
223+
224+
// insertFreeSpan inserts a new span of free blocks into the span tree.
225+
func insertFreeSpan(block gcBlock, len uintptr) {
226+
if len == 0 {
227+
return
228+
}
229+
230+
// Search for the appropriate root insertion point.
231+
rins := &gcFreeSpans
232+
for *rins != nil && (*rins).len < len {
233+
rins = &(*rins).nextSize
234+
}
235+
236+
if root := *rins; root != nil && root.len == len {
237+
// There is already a root for this size.
238+
// Add a span to this root.
239+
span := (*gcFreeSpan)(block.pointer())
240+
*span = gcFreeSpan{
241+
next: root.next,
242+
}
243+
root.next = span
244+
return
245+
}
246+
247+
// Create a new root for this size.
248+
root := (*gcFreeRootSpan)(block.pointer())
249+
*root = gcFreeRootSpan{
250+
len: len,
251+
nextSize: *rins,
252+
}
253+
*rins = root
254+
}
255+
256+
// rebuildSpanTree rebuilds the free span tree.
257+
// This is used after the GC sweep.
258+
func rebuildSpanTree() {
259+
// Clear the existing span tree.
260+
gcFreeSpans = nil
261+
262+
// Search for all free blocks.
263+
for block := gcBlock(0); block < endBlock; block++ {
264+
if block.state() != blockStateFree {
265+
// This block is in use.
266+
continue
267+
}
268+
269+
// This is the first block in a free span.
270+
spanStart := block
271+
272+
// Find the end of the free span.
273+
for block < endBlock && block.state() == blockStateFree {
274+
block++
275+
}
276+
277+
// Add the span to the tree.
278+
insertFreeSpan(spanStart, uintptr(block-spanStart))
279+
}
280+
}
281+
282+
// allocBlocks allocates a series of gc blocks.
283+
// It attempts to use the smallest possible free span.
284+
func allocBlocks(len uintptr) (gcBlock, bool) {
285+
// Search for a root with a span length of at least len.
286+
rins := &gcFreeSpans
287+
for *rins != nil && (*rins).len < len {
288+
rins = &(*rins).nextSize
289+
}
290+
if *rins == nil {
291+
// There are no sufficiently-large free spans.
292+
return 0, false
293+
}
294+
295+
// Get a free span.
296+
root := *rins
297+
var span *gcFreeSpan
298+
if span = root.next; span != nil {
299+
// Pop a leaf span off of the tree.
300+
root.next = span.next
301+
} else {
302+
// There is only one free span of this size.
303+
// Extract the root.
304+
*rins = root.nextSize
305+
span = &root.gcFreeSpan
306+
}
307+
308+
if root.len > len {
309+
// The span is longer than requested.
310+
// Insert the remainder of the span.
311+
insertFreeSpan(span.gcBlock()+gcBlock(len), root.len-len)
312+
}
313+
314+
return span.gcBlock(), true
315+
}
316+
180317
// Initialize the memory allocator.
181-
// No memory may be allocated before this is called. That means the runtime and
182-
// any packages the runtime depends upon may not allocate memory during package
183-
// initialization.
318+
// No memory may be allocated before this is called.
184319
func initHeap() {
185320
totalSize := heapEnd - heapStart
186321

@@ -208,6 +343,9 @@ func initHeap() {
208343

209344
// Set all block states to 'free'.
210345
memzero(unsafe.Pointer(heapStart), metadataSize)
346+
347+
// Insert an initial free span.
348+
insertFreeSpan(0, numBlocks)
211349
}
212350

213351
// alloc tries to find some free space on the heap, possibly doing a garbage
@@ -219,66 +357,36 @@ func alloc(size uintptr) unsafe.Pointer {
219357
}
220358

221359
neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock
360+
if neededBlocks > uintptr(endBlock) {
361+
runtimePanic("oversized allocation")
362+
}
222363

223-
// Continue looping until a run of free blocks has been found that fits the
224-
// requested size.
225-
index := nextAlloc
226-
numFreeBlocks := uintptr(0)
227-
heapScanCount := uint8(0)
228-
for {
229-
if index == nextAlloc {
230-
if heapScanCount == 0 {
231-
heapScanCount = 1
232-
} else if heapScanCount == 1 {
233-
// The entire heap has been searched for free memory, but none
234-
// could be found. Run a garbage collection cycle to reclaim
235-
// free memory and try again.
236-
heapScanCount = 2
237-
GC()
238-
} else {
239-
// Even after garbage collection, no free memory could be found.
240-
runtimePanic("out of memory")
241-
}
364+
var gcRun bool
365+
tryAlloc:
366+
// Find a span of free blocks.
367+
block, ok := allocBlocks(neededBlocks)
368+
if !ok {
369+
// There is no sufficiently large span of free blocks available.
370+
371+
if !gcRun {
372+
// Run the garbage collector and try again.
373+
GC()
374+
gcRun = true
375+
goto tryAlloc
242376
}
243377

244-
// Wrap around the end of the heap.
245-
if index == endBlock {
246-
index = 0
247-
// Reset numFreeBlocks as allocations cannot wrap.
248-
numFreeBlocks = 0
249-
}
378+
// There is not enough space for the allocation.
379+
runtimePanic("out of memory")
380+
}
250381

251-
// Is the block we're looking at free?
252-
if index.state() != blockStateFree {
253-
// This block is in use. Try again from this point.
254-
numFreeBlocks = 0
255-
index++
256-
continue
257-
}
258-
numFreeBlocks++
259-
index++
260-
261-
// Are we finished?
262-
if numFreeBlocks == neededBlocks {
263-
// Found a big enough range of free blocks!
264-
nextAlloc = index
265-
thisAlloc := index - gcBlock(neededBlocks)
266-
if gcDebug {
267-
println("found memory:", thisAlloc.pointer(), int(size))
268-
}
382+
// Set the states of the blocks as allocated.
383+
block.setAlloc(neededBlocks)
269384

270-
// Set the following blocks as being allocated.
271-
thisAlloc.setState(blockStateHead)
272-
for i := thisAlloc + 1; i != nextAlloc; i++ {
273-
i.setState(blockStateTail)
274-
}
385+
// Clear the allocation.
386+
pointer := block.pointer()
387+
memzero(pointer, size)
275388

276-
// Return a pointer to this allocation.
277-
pointer := thisAlloc.pointer()
278-
memzero(pointer, size)
279-
return pointer
280-
}
281-
}
389+
return pointer
282390
}
283391

284392
func free(ptr unsafe.Pointer) {
@@ -300,6 +408,9 @@ func GC() {
300408
// the next collection cycle.
301409
sweep()
302410

411+
// Build a tree of free memory spans.
412+
rebuildSpanTree()
413+
303414
// Show how much has been sweeped, for debugging.
304415
if gcDebug {
305416
dumpHeap()

0 commit comments

Comments
 (0)