ELSEIF
Your brief EB
178 stories from 108 feeds 352 clusters Refreshed 8 minutes ago next pull 02:22

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.

WHY IT MATTERS

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 source

The three things worth knowing

01

Old allocator held 4.3 MiB of address space per 10 000 cross-thread blocks, growing without bound.

02

New allocator binds each 8 MiB arena to one thread, enabling O(1) free-list scans and immediate region reuse.

03

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.

ORIGINAL ANALYSIS

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 contained

THE CLUSTER

Same story, 1 feed.

ORDERED BY FIRST SEEN
ponylang.io via Lobsters Pony's Arena Allocator Open ↗