Skip to content

WasmGrowAndClaim default uses 2-10x more linear memory than WasmGrowAndExtend in fragmenting workloads #51

Description

@jlucaso1

Using the dynamic wasm allocator (new_wasm_dynamic_allocator, i.e. WasmGrowAndClaim) in a long-running module, I measured it holding a lot more linear memory than WasmGrowAndExtend on the same workload. Since wasm memory only ever grows, that high-water mark is permanent, so I wanted to put the numbers in front of you and ask whether the default should change.

Pages of linear memory grown (1 page = 64 KiB), each workload run against a fresh allocator per source:

workload Claim Extend ratio
ratchet 1705 171 10.0x
random_actions 1516 720 2.1x
page_churn 3 3 1.0x

ratchet is a buffer that grows by about a page each step, freeing the previous one; random_actions is the random alloc/dealloc/realloc churn from the regression suite; page_churn is a 65520-byte alloc/free loop with one small pinned allocation.

The difference is that WasmGrowAndClaim::acquire always claims a fresh heap, even though memory.grow returns a region contiguous with the previous heap end, so a freed heap can never coalesce with a later grow. WasmGrowAndExtend checks old_end == new_base and extends, so freed tail space merges with the new pages. The two are equal when allocations are uniform and fully freed (page_churn) and diverge as soon as the live set ratchets up or fragments.

The cost of Extend is code size: the optimized dynamic module is +150 bytes (1627 to 1777, about +9%), which I assume is why Claim is the documented default. So it's a real tradeoff, 150 bytes of code against 2-10x linear memory for long-running modules, and your call which side the default lands on.

A few ways to go: flip new_wasm_dynamic_allocator to WasmGrowAndExtend; fold the two sources into one (extend-when-contiguous, which is what Extend already does, falling back to claim otherwise); or keep both and just call out the tradeoff more loudly for long-running use. One thing I'd argue against is geometric or over-provisioned growth: since wasm can't shrink, that only raises the permanent high-water mark, so the lever here is coalescing, not growth policy.

I measured this with a small wasm-bindgen test that runs each workload under a fresh allocator and reads the memory_size() delta. Happy to send it as a PR or fold it into the wasm regression tests if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions