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...