Skip to content

Commit b941eb4

Browse files
committed
runtime (gc): bundle small allocations together
This reduces the overhead of small allocations. Previously a pointer sized allocation would take up an entire block - leaving 3 pointer-widths of wasted space. Small allocations are now bundled together, and are treated as a single object for garbage-collection purposes. These could be tracked seperately, but not without paying for space to store additional garbage collector metadata.
1 parent c4fd19b commit b941eb4

File tree

1 file changed

+88
-3
lines changed

1 file changed

+88
-3
lines changed

src/runtime/gc_conservative.go

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,92 @@ func initHeap() {
213213
// collection cycle if needed. If no space is free, it panics.
214214
//go:noinline
215215
func alloc(size uintptr) unsafe.Pointer {
216-
if size == 0 {
217-
return unsafe.Pointer(&zeroSizedAlloc)
216+
var ptr unsafe.Pointer
217+
switch {
218+
case size == 0:
219+
// Use a fixed location for all zero-sized allocations.
220+
ptr = unsafe.Pointer(&zeroSizedAlloc)
221+
case size < bytesPerBlock:
222+
// Bundle small allocations together.
223+
ptr = smallAlloc(size)
224+
default:
225+
// Allocate multiple memory blocks.
226+
ptr = bigAlloc(size)
227+
}
228+
229+
// Zero the allocation.
230+
memzero(ptr, size)
231+
232+
return ptr
233+
}
234+
235+
// alignMask computes a bit mask which contains all bits which must be zero in the maximum alignment of a pointer of the given size.
236+
// This assumes that size is a small value.
237+
func alignMask(size uintptr) uintptr {
238+
x := size
239+
240+
// Make each bit into the logical or of all of the bits above it.
241+
x = x | (x >> 1)
242+
x = x | (x >> 2)
243+
x = x | (x >> 4)
244+
x = x | (x >> 8)
245+
x = x | (x >> 16)
246+
x = x | (x >> 32)
247+
248+
// The highest bit's value is the alignment.
249+
// Shifting down gives us a mask that is one less than the alignment.
250+
return x >> 1
251+
}
252+
253+
// smallBlock is a pointer to a memory block that can be divided up for small (sub-block-size) allocations.
254+
var smallBlock *[bytesPerBlock]byte
255+
256+
// smallBlockAvailable is the amount of remaining available space in smallBlock.
257+
var smallBlockAvailable uintptr
258+
259+
// smallAlloc handles allocations that are smaller than a memory block.
260+
// Several allocations are bundled together into a single memory block.
261+
// These allocations are collectively treated as a single object by the garbage collector.
262+
func smallAlloc(size uintptr) unsafe.Pointer {
263+
// Compute alignment mask.
264+
align := alignMask(size)
265+
266+
if size > smallBlockAvailable&^align {
267+
// There is not enough space in smallBlock for this allocation.
268+
// Acquire a new block.
269+
block := bigAlloc(bytesPerBlock)
270+
271+
if smallBlockAvailable > bytesPerBlock-size {
272+
// After this allocation, there will be less leftover space in this block than in the old block.
273+
// Do not attempt to re-use the new block.
274+
return block
275+
}
276+
277+
// The new block will have more extra space than the old one.
278+
// Discard the old block, and replace it with a new one.
279+
smallBlock = (*[bytesPerBlock]byte)(block)
280+
smallBlockAvailable = bytesPerBlock
218281
}
219282

283+
// Prepend any necessary padding to the allocation.
284+
smallBlockAvailable &^= align
285+
286+
// Extract a pointer to the base of the allocation.
287+
ptr := unsafe.Pointer(&smallBlock[bytesPerBlock-smallBlockAvailable])
288+
289+
// Subtract used space.
290+
smallBlockAvailable -= size
291+
292+
// If the block has been expended, discard it so it may eventually be garbage collected.
293+
if smallBlockAvailable == 0 {
294+
smallBlock = nil
295+
}
296+
297+
return ptr
298+
}
299+
300+
// bigAlloc allocates a series of consecutive memory blocks, running the garbage collector if necessary.
301+
func bigAlloc(size uintptr) unsafe.Pointer {
220302
neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock
221303

222304
// Continue looping until a run of free blocks has been found that fits the
@@ -274,7 +356,6 @@ func alloc(size uintptr) unsafe.Pointer {
274356

275357
// Return a pointer to this allocation.
276358
pointer := thisAlloc.pointer()
277-
memzero(pointer, size)
278359
return pointer
279360
}
280361
}
@@ -290,6 +371,10 @@ func GC() {
290371
println("running collection cycle...")
291372
}
292373

374+
// Ditch the small allocations bundle block to avoid keeping it alive if the bundled allocations are dead.
375+
smallBlock = nil
376+
smallBlockAvailable = 0
377+
293378
// Mark phase: mark all reachable objects, recursively.
294379
markGlobals()
295380
markStack()

0 commit comments

Comments
 (0)