Skip to content

Commit 1ad3c15

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 cbaa58a commit 1ad3c15

File tree

1 file changed

+79
-3
lines changed

1 file changed

+79
-3
lines changed

src/runtime/gc_conservative.go

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,83 @@ 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+
func alignMask(size uintptr) uintptr {
237+
switch {
238+
case size <= unsafe.Sizeof(uint8(0)):
239+
return unsafe.Alignof(uint8(0)) - 1
240+
case size <= unsafe.Sizeof(uint16(0)):
241+
return unsafe.Alignof(uint16(0)) - 1
242+
case size <= unsafe.Sizeof(uint32(0)):
243+
return unsafe.Alignof(uint32(0)) - 1
244+
default:
245+
return unsafe.Alignof(uint64(0)) - 1
246+
}
247+
}
248+
249+
// smallBlock is a pointer to a memory block that can be divided up for small (sub-block-size) allocations.
250+
var smallBlock *[bytesPerBlock]byte
251+
252+
// smallBlockAvailable is the amount of remaining available space in smallBlock.
253+
var smallBlockAvailable uintptr
254+
255+
// smallAlloc handles allocations that are smaller than a memory block.
256+
// Several allocations are bundled together into a single memory block.
257+
// These allocations are collectively treated as a single object by the garbage collector.
258+
func smallAlloc(size uintptr) unsafe.Pointer {
259+
// Compute alignment mask.
260+
align := alignMask(size)
261+
262+
if size > smallBlockAvailable&^align {
263+
// There is not enough space in smallBlock for this allocation.
264+
// Acquire a new block.
265+
block := bigAlloc(bytesPerBlock)
266+
267+
if smallBlockAvailable > bytesPerBlock-size {
268+
// After this allocation, there will be less leftover space in this block than in the old block.
269+
// Do not attempt to re-use the new block.
270+
return block
271+
}
272+
273+
// The new block will have more extra space than the old one.
274+
// Discard the old block, and replace it with a new one.
275+
smallBlock = (*[bytesPerBlock]byte)(block)
276+
smallBlockAvailable = bytesPerBlock
218277
}
219278

279+
// Prepend any necessary padding to the allocation.
280+
smallBlockAvailable &^= align
281+
282+
// Extract a pointer to the base of the allocation.
283+
ptr := unsafe.Pointer(&smallBlock[bytesPerBlock-smallBlockAvailable])
284+
285+
// Subtract used space.
286+
smallBlockAvailable -= size
287+
288+
return ptr
289+
}
290+
291+
// bigAlloc allocates a series of consecutive memory blocks, running the garbage collector if necessary.
292+
func bigAlloc(size uintptr) unsafe.Pointer {
220293
neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock
221294

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

275348
// Return a pointer to this allocation.
276349
pointer := thisAlloc.pointer()
277-
memzero(pointer, size)
278350
return pointer
279351
}
280352
}
@@ -290,6 +362,10 @@ func GC() {
290362
println("running collection cycle...")
291363
}
292364

365+
// Ditch the small allocations bundle block to avoid keeping it alive if the bundled allocations are dead.
366+
smallBlock = nil
367+
smallBlockAvailable = 0
368+
293369
// Mark phase: mark all reachable objects, recursively.
294370
markGlobals()
295371
markStack()

0 commit comments

Comments
 (0)