TECH Signal 395
Pony runtime replaces global allocator with per-thread arenas to fix unbounded memory growth
Illustration only Photo by Ivan Bandura on Unsplash
Pony’s new arena allocator eliminates unbounded memory growth and O(n) free-list walks by binding each 8 MiB arena to a single thread.
Memory allocators are a silent bottleneck in high-concurrency runtimes. A global pool that forces every thread to walk a shared free list can turn a micro-benchmark into a multi-minute stall. The fix, per-thread arenas with batched cross-thread frees, is now live in Pony, removing the risk of unbounded address-space consumption under message-passing workloads.
Written by elseif from the cluster below · every claim links back to a sourceThe three things worth knowing
Old allocator held 4.3 MiB of address space per 10 000 cross-thread blocks, growing without bound.
New allocator binds each 8 MiB arena to one thread, enabling O(1) free-list scans and immediate region reuse.
Empty arenas return physical pages to the OS but keep address space parked for reuse, preventing fragmentation.
THE READ
What the cluster adds up to.
The event is a concrete change in Pony’s memory allocator. The old design used a single global pool with per-size-class free lists shared by every thread. Under message-passing workloads, blocks freed on a different thread than the one that allocated them stayed reserved, causing address-space consumption to grow without bound. The new allocator replaces the global pool with per-thread arenas, eliminating the unbounded growth and the O(n) free-list walk that could stall allocations for minutes.
Each thread now owns its 8 MiB arenas. The owning thread tracks allocations and frees in a bitmap, so two adjacent free units are already merged in the bitmap representation. This removes the old allocator’s merge step and the sorted free list that caused the O(n) walk. Cross-thread frees are batched and routed to the owning thread, so the global lock is gone. The cost is a small increase in bookkeeping per thread, but the payoff is bounded memory use and predictable allocation latency.
Regions are 256 MiB chunks requested from the OS and never unmapped. Threads carve arenas from regions; when an arena empties, its physical pages return to the OS but its address space stays parked in the region. This keeps the region list stable, giving the allocator a memory-safety guarantee without the cost of unmapping. The design trades a small amount of address-space overhead for the ability to reuse arenas instantly and avoid the fragmentation that would come from unmapping and remapping.
The change stops working when the workload is dominated by allocations that span multiple arenas. Large allocations still use the OS directly, so the per-thread arena design does not help them. It also does not help workloads that allocate and free objects of wildly different sizes in the same thread; the bitmap representation is optimized for power-of-two size classes, so odd-sized objects still require a fallback path. Finally, the design assumes that threads are long-lived; short-lived threads that allocate and exit quickly may leave empty arenas that never get reused.
Written by elseif from the cluster below · checked for specifics the sources never containedTHE CLUSTER