@@ -213,10 +213,83 @@ 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+ 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