@@ -51,7 +51,7 @@ const (
5151
5252var (
5353 metadataStart unsafe.Pointer // pointer to the start of the heap metadata
54- nextAlloc gcBlock // the next block that should be tried by the allocator
54+ freeRanges * freeRange // freeRanges is a linked list of free block ranges
5555 endBlock gcBlock // the block just past the end of the available space
5656 gcTotalAlloc uint64 // total number of bytes allocated
5757 gcTotalBlocks uint64 // total number of allocated blocks
@@ -225,6 +225,99 @@ func (b gcBlock) unmark() {
225225 }
226226}
227227
228+ // freeRange is a node on the outer list of range lengths.
229+ // The free ranges are structured as two nested singly-linked lists:
230+ // - The outer level (freeRange) has one entry for each unique range length.
231+ // - The inner level (freeRangeMore) has one entry for each additional range of the same length.
232+ // This two-level structure ensures that insertion/removal times are proportional to the requested length.
233+ type freeRange struct {
234+ // len is the length of this free range.
235+ len uintptr
236+
237+ // nextLen is the next longer free range.
238+ nextLen * freeRange
239+
240+ // nextWithLen is the next free range with this length.
241+ nextWithLen * freeRangeMore
242+ }
243+
244+ // freeRangeMore is a node on the inner list of equal-length ranges.
245+ type freeRangeMore struct {
246+ next * freeRangeMore
247+ }
248+
249+ // insertFreeRange inserts a range of len blocks starting at ptr into the free list.
250+ func insertFreeRange (ptr unsafe.Pointer , len uintptr ) {
251+ if gcAsserts && len == 0 {
252+ runtimePanic ("gc: insert 0-length free range" )
253+ }
254+
255+ // Find the insertion point by length.
256+ // Skip until the next range is at least the target length.
257+ insDst := & freeRanges
258+ for * insDst != nil && (* insDst ).len < len {
259+ insDst = & (* insDst ).nextLen
260+ }
261+
262+ // Create the new free range.
263+ next := * insDst
264+ if next != nil && next .len == len {
265+ // Insert into the list with this length.
266+ newRange := (* freeRangeMore )(ptr )
267+ newRange .next = next .nextWithLen
268+ next .nextWithLen = newRange
269+ } else {
270+ // Insert into the list of lengths.
271+ newRange := (* freeRange )(ptr )
272+ * newRange = freeRange {
273+ len : len ,
274+ nextLen : next ,
275+ nextWithLen : nil ,
276+ }
277+ * insDst = newRange
278+ }
279+ }
280+
281+ // popFreeRange removes a range of len blocks from the freeRanges list.
282+ // It returns nil if there are no sufficiently long ranges.
283+ func popFreeRange (len uintptr ) unsafe.Pointer {
284+ if gcAsserts && len == 0 {
285+ runtimePanic ("gc: pop 0-length free range" )
286+ }
287+
288+ // Find the removal point by length.
289+ // Skip until the next range is at least the target length.
290+ remDst := & freeRanges
291+ for * remDst != nil && (* remDst ).len < len {
292+ remDst = & (* remDst ).nextLen
293+ }
294+
295+ rangeWithLength := * remDst
296+ if rangeWithLength == nil {
297+ // No ranges are long enough.
298+ return nil
299+ }
300+ removedLen := rangeWithLength .len
301+
302+ // Remove the range.
303+ var ptr unsafe.Pointer
304+ if nextWithLen := rangeWithLength .nextWithLen ; nextWithLen != nil {
305+ // Remove from the list with this length.
306+ rangeWithLength .nextWithLen = nextWithLen .next
307+ ptr = unsafe .Pointer (nextWithLen )
308+ } else {
309+ // Remove from the list of lengths.
310+ * remDst = rangeWithLength .nextLen
311+ ptr = unsafe .Pointer (rangeWithLength )
312+ }
313+
314+ if removedLen > len {
315+ // Insert the leftover range.
316+ insertFreeRange (unsafe .Add (ptr , len * bytesPerBlock ), removedLen - len )
317+ }
318+ return ptr
319+ }
320+
228321func isOnHeap (ptr uintptr ) bool {
229322 return ptr >= heapStart && ptr < uintptr (metadataStart )
230323}
@@ -239,6 +332,9 @@ func initHeap() {
239332 // Set all block states to 'free'.
240333 metadataSize := heapEnd - uintptr (metadataStart )
241334 memzero (unsafe .Pointer (metadataStart ), metadataSize )
335+
336+ // Rebuild the free ranges list.
337+ buildFreeRanges ()
242338}
243339
244340// setHeapEnd is called to expand the heap. The heap can only grow, not shrink.
@@ -270,6 +366,9 @@ func setHeapEnd(newHeapEnd uintptr) {
270366 if gcAsserts && uintptr (metadataStart ) < uintptr (oldMetadataStart )+ oldMetadataSize {
271367 runtimePanic ("gc: heap did not grow enough at once" )
272368 }
369+
370+ // Rebuild the free ranges list.
371+ buildFreeRanges ()
273372}
274373
275374// calculateHeapAddresses initializes variables such as metadataStart and
@@ -338,100 +437,67 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
338437 gcMallocs ++
339438 gcTotalBlocks += uint64 (neededBlocks )
340439
341- // Continue looping until a run of free blocks has been found that fits the
342- // requested size.
343- index := nextAlloc
344- numFreeBlocks := uintptr (0 )
345- heapScanCount := uint8 (0 )
440+ // Acquire a range of free blocks.
441+ var ranGC bool
442+ var grewHeap bool
443+ var pointer unsafe.Pointer
346444 for {
347- if index == nextAlloc {
348- if heapScanCount == 0 {
349- heapScanCount = 1
350- } else if heapScanCount == 1 {
351- // The entire heap has been searched for free memory, but none
352- // could be found. Run a garbage collection cycle to reclaim
353- // free memory and try again.
354- heapScanCount = 2
355- freeBytes := runGC ()
356- heapSize := uintptr (metadataStart ) - heapStart
357- if freeBytes < heapSize / 3 {
358- // Ensure there is at least 33% headroom.
359- // This percentage was arbitrarily chosen, and may need to
360- // be tuned in the future.
361- growHeap ()
362- }
363- } else {
364- // Even after garbage collection, no free memory could be found.
365- // Try to increase heap size.
366- if growHeap () {
367- // Success, the heap was increased in size. Try again with a
368- // larger heap.
369- } else {
370- // Unfortunately the heap could not be increased. This
371- // happens on baremetal systems for example (where all
372- // available RAM has already been dedicated to the heap).
373- runtimePanicAt (returnAddress (0 ), "out of memory" )
374- }
375- }
445+ pointer = popFreeRange (neededBlocks )
446+ if pointer != nil {
447+ break
376448 }
377449
378- // Wrap around the end of the heap.
379- if index == endBlock {
380- index = 0
381- // Reset numFreeBlocks as allocations cannot wrap.
382- numFreeBlocks = 0
383- // In rare cases, the initial heap might be so small that there are
384- // no blocks at all. In this case, it's better to jump back to the
385- // start of the loop and try again, until the GC realizes there is
386- // no memory and grows the heap .
387- // This can sometimes happen on WebAssembly, where the initial heap
388- // is created by whatever is left on the last memory page.
450+ if ! ranGC {
451+ // Run the collector and try again.
452+ freeBytes := runGC ()
453+ ranGC = true
454+ heapSize := uintptr ( metadataStart ) - heapStart
455+ if freeBytes < heapSize / 3 {
456+ // Ensure there is at least 33% headroom.
457+ // This percentage was arbitrarily chosen, and may need to
458+ // be tuned in the future .
459+ growHeap ()
460+ }
389461 continue
390462 }
391463
392- // Is the block we're looking at free?
393- if index .state () != blockStateFree {
394- // This block is in use. Try again from this point.
395- numFreeBlocks = 0
396- index ++
464+ if gcDebug && ! grewHeap {
465+ println ("grow heap for request:" , uint (neededBlocks ))
466+ dumpFreeRangeCounts ()
467+ }
468+ if growHeap () {
469+ grewHeap = true
397470 continue
398471 }
399- numFreeBlocks ++
400- index ++
401-
402- // Are we finished?
403- if numFreeBlocks == neededBlocks {
404- // Found a big enough range of free blocks!
405- nextAlloc = index
406- thisAlloc := index - gcBlock (neededBlocks )
407- if gcDebug {
408- println ("found memory:" , thisAlloc .pointer (), int (size ))
409- }
410472
411- // Set the following blocks as being allocated.
412- thisAlloc . setState ( blockStateHead )
413- for i := thisAlloc + 1 ; i != nextAlloc ; i ++ {
414- i . setState ( blockStateTail )
415- }
473+ // Unfortunately the heap could not be increased. This
474+ // happens on baremetal systems for example (where all
475+ // available RAM has already been dedicated to the heap).
476+ runtimePanicAt ( returnAddress ( 0 ), "out of memory" )
477+ }
416478
417- // We've claimed this allocation, now we can unlock the heap.
418- gcLock .Unlock ()
419-
420- // Return a pointer to this allocation.
421- pointer := thisAlloc .pointer ()
422- if preciseHeap {
423- // Store the object layout at the start of the object.
424- // TODO: this wastes a little bit of space on systems with
425- // larger-than-pointer alignment requirements.
426- * (* unsafe .Pointer )(pointer ) = layout
427- add := align (unsafe .Sizeof (layout ))
428- pointer = unsafe .Add (pointer , add )
429- size -= add
430- }
431- memzero (pointer , size )
432- return pointer
433- }
479+ // Set the backing blocks as being allocated.
480+ block := blockFromAddr (uintptr (pointer ))
481+ block .setState (blockStateHead )
482+ for i := block + 1 ; i != block + gcBlock (neededBlocks ); i ++ {
483+ i .setState (blockStateTail )
434484 }
485+
486+ // We've claimed this allocation, now we can unlock the heap.
487+ gcLock .Unlock ()
488+
489+ // Return a pointer to this allocation.
490+ if preciseHeap {
491+ // Store the object layout at the start of the object.
492+ // TODO: this wastes a little bit of space on systems with
493+ // larger-than-pointer alignment requirements.
494+ * (* unsafe .Pointer )(pointer ) = layout
495+ add := align (unsafe .Sizeof (layout ))
496+ pointer = unsafe .Add (pointer , add )
497+ size -= add
498+ }
499+ memzero (pointer , size )
500+ return pointer
435501}
436502
437503func realloc (ptr unsafe.Pointer , size uintptr ) unsafe.Pointer {
@@ -518,6 +584,9 @@ func runGC() (freeBytes uintptr) {
518584 // the next collection cycle.
519585 freeBytes = sweep ()
520586
587+ // Rebuild the free ranges list.
588+ buildFreeRanges ()
589+
521590 // Show how much has been sweeped, for debugging.
522591 if gcDebug {
523592 dumpHeap ()
@@ -717,6 +786,46 @@ func sweep() (freeBytes uintptr) {
717786 return
718787}
719788
789+ // buildFreeRanges rebuilds the freeRanges list.
790+ // This must be called after a GC sweep or heap grow.
791+ func buildFreeRanges () {
792+ freeRanges = nil
793+ block := endBlock
794+ for {
795+ // Skip backwards over occupied blocks.
796+ for block > 0 && (block - 1 ).state () != blockStateFree {
797+ block --
798+ }
799+ if block == 0 {
800+ break
801+ }
802+
803+ // Find the start of the free range.
804+ end := block
805+ for block > 0 && (block - 1 ).state () == blockStateFree {
806+ block --
807+ }
808+
809+ // Insert the free range.
810+ insertFreeRange (block .pointer (), uintptr (end - block ))
811+ }
812+
813+ if gcDebug {
814+ println ("free ranges after rebuild:" )
815+ dumpFreeRangeCounts ()
816+ }
817+ }
818+
819+ func dumpFreeRangeCounts () {
820+ for rangeWithLength := freeRanges ; rangeWithLength != nil ; rangeWithLength = rangeWithLength .nextLen {
821+ totalRanges := uintptr (1 )
822+ for nextWithLen := rangeWithLength .nextWithLen ; nextWithLen != nil ; nextWithLen = nextWithLen .next {
823+ totalRanges ++
824+ }
825+ println ("-" , uint (rangeWithLength .len ), "x" , uint (totalRanges ))
826+ }
827+ }
828+
720829// dumpHeap can be used for debugging purposes. It dumps the state of each heap
721830// block to standard output.
722831func dumpHeap () {
0 commit comments