@@ -213,10 +213,92 @@ func initHeap() {
213213// collection cycle if needed. If no space is free, it panics.
214214//go:noinline
215215func 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