libghostty: Use custom memory pool for WASM

tosh1 pts0 comments

libghostty: use custom memory pool for Wasm, reduce terminal memory by ~75% by mitchellh · Pull Request #13865 · ghostty-org/ghostty · GitHub

//voltron/pull_requests_fragments/pull_request_layout" data-turbo-transient="true" />

Skip to content

Search/

Sign in<br>Sign upAppearance settings

You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.

Dismiss alert

{{ message }}

Uh oh!

There was an error while loading. Please reload this page.

ghostty-org

ghostty

Public

Notifications<br>You must be signed in to change notification settings

Fork<br>3.3k

Star<br>59.8k

Merged<br>libghostty: use custom memory pool for Wasm, reduce terminal memory by ~75%#13865mitchellh merged 1 commit intomainghostty-org/ghostty:mainfrom push-sqnkmoxmoypzghostty-org/ghostty:push-sqnkmoxmoypzCopy head branch name to clipboard

Conversation

mitchellh

commented

Aug 17, 2026

edited

Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link

Copy Markdown

Contributor

A custom memory pool for Wasm that grows by exactly one item size per growth and shares the pool across the entire Wasm-module instead of per-terminal.

Some background on why std.heap.MemoryPool is considered harmful for WebAssembly:

First, the std.heap.MemoryPool grows 1.5x at each growth point. The backing allocator for that is usually a GPA which is the BrkAllocator for wasm. This grows by power-of-two big-allocation slots. If you pair these together you get a massive permanent linear memory growth.

On non-wasm targets, the memory growth doesn't matter because these are virtual memory mappings that don't cost physical memory, but wasm doesn't work that way. Also on native targets, the syscalls to allocate memory are very expensive (relatively), so it makes sense to allocate large virtual memory chunks and avoid them. Again, wasm doesn't work this way.

Second, we were using one pool per terminal. On wasm, this meant that we paid for the free list N times. On non-wasm, this makes sense because the synchronization overhead has so far been measurable enough under load to be prohibitive (although, I'm still skeptical about this and want to look into it). On wasm, we build single-threaded modules, so we can use a global free list without any extra overhead.

Benchmarks

80x24 terminal with 1000-line scrollack processing 16MB of plain ASCII.

Scenario<br>Before<br>After

Fresh instance<br>0.56 MiB<br>0.56 MiB

First terminal_new (delta)<br>+3.44 MiB<br>+0.88 MiB

One filled terminal (total)<br>4.00 MiB<br>1.88 MiB

Each additional filled terminal<br>+3.00 MiB<br>+0.44 MiB

5 filled terminals (total)<br>16.00 MiB<br>4.06 MiB

Throughput numbers are unchanged on wasm and native (to be expected in the latter because this is all gated on

wasm).

Note I'm still very much optimizing the above numbers! This is just my first big win.

AI usage: None used except to validate and judge.

-->

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

-->

❤️<br>elias8, gildrb, ryanconnaughton, and lamuria reacted with heart emoji<br>🚀<br>roy-corentin and mattrighetti reacted with rocket emoji

All reactions

❤️<br>4 reactions

🚀<br>2 reactions

libghostty: use custom memory pool for Wasm

492c260

A custom memory pool for Wasm that grows by exactly one item size per<br>growth and shares the pool across the entire Wasm-module instead of<br>per-terminal.

Some background on why `std.heap.MemoryPool` is considered harmful for<br>WebAssembly:

First, the std.heap.MemoryPool grows 1.5x at each growth point. The backing<br>allocator for that is usually a GPA which is the BrkAllocator for wasm.<br>This grows by power-of-two big-allocation slots. If you pair these together<br>you get a massive permanent linear memory growth. On non-wasm targets,<br>this doesn't matter because these are virtual memory mappings that don't<br>cost physical memory, but wasm doesn't work that way.

Second, we were using one pool per terminal. On wasm, this meant that<br>we paid for the free list N times. On non-wasm, this makes sense because<br>the synchronization overhead has so far been measurable enough under<br>load to be prohibitive (although, I'm still skeptical about this and want<br>to look into it). On wasm, we build single-threaded modules, so we can use<br>a global free list without any extra overhead.

## Benchmarks

80x24 terminal with 1000-line scrollack processing 16MB of plain ASCII.

| Scenario | Before | After |<br>| ------------------------------- | --------: | --------: |<br>| Fresh instance | 0.56 MiB | 0.56 MiB |<br>| First `terminal_new` (delta) | +3.44 MiB | +0.88 MiB |<br>| One filled terminal (total) | 4.00 MiB | 1.88 MiB |<br>| Each additional filled terminal | +3.00 MiB | +0.44 MiB |<br>| 5 filled terminals (total) | 16.00 MiB | 4.06 MiB |

Throughput numbers are unchanged on wasm and native (to be expected in<br>the latter because this is all gated on...

wasm memory terminal pool growth custom

Related Articles