Skip to content

perf(desktop-gpui): use mimalloc as the global allocator on Windows - #3402

Open
diiviikk5 wants to merge 2 commits into
CapSoftware:mainfrom
diiviikk5:perf/gpui-windows-mimalloc
Open

diiviikk5 wants to merge 2 commits into
CapSoftware:mainfrom
diiviikk5:perf/gpui-windows-mimalloc

Conversation

@diiviikk5

@diiviikk5 diiviikk5 commented Oct 26, 2026 •

Copy link
Copy Markdown

On Windows the editor preview uses the RGBA path, which allocates two frame-sized buffers for every presented frame:

  • PendingReadback::wait (crates/rendering/src/frame_pipeline.rs) starts a fresh Vec on the preview path. Only exports recycle it.
  • frame_image (apps/desktop-gpui/src/editor_window.rs) allocates a tight BGRA copy through unpad_rgba_to_bgra.

Each is 3.5 MB at the default half-size preview and 8 MB at 1080p. The Windows heap serves blocks that large directly from VirtualAlloc and returns them on free, so every frame pays demand-zero page faults on both buffers. mimalloc keeps freed segments resident and reuses them.

This sets mimalloc as the global allocator on Windows only. macOS and Linux are unchanged.

Numbers

examples/frame-alloc-benchmark.rs (added here) repeats that per-frame pattern against each allocator: padded readback, tight copy, both written in full and then freed. 600 frames per case after 30 warm-up frames, 10 runs, Ryzen 5 4600H, Windows 11 26200:

Preview System heap, µs/frame mimalloc, µs/frame Speedup Page faults/frame
Half (1248×702) 1631 [1546–2280] 227 [173–688] 7.5× [2.6–12.7] 1737 → 0
1080p 4459 [3741–7136] 1434 [1373–1874] 2.9× [2.6–4.8] 4060 → 0–3.4
4K 16421 [14582–20440] 6345 [5846–8152] 2.5× [1.9–2.9] 16233 → 41–68

Medians, with min–max across the 10 runs in brackets. This is a laptop, so the timings are noisy. The fault counts are not: they varied by at most 3 across all runs, and at half size 1737 is every 4 KB page of both buffers, every frame. They matched the earlier version of this benchmark, which only passed one byte to black_box, so that version was not skipping work.

What I haven't measured is end-to-end playback (process CPU, fps, working set) in the app itself. My Windows machine can't build the full app yet (ort needs MSVC 14.42+). If someone can run the A/B below on Windows, that would settle how much of this reaches playback. Expect a somewhat higher steady working set, since mimalloc keeps segments mapped.

cargo run --release --example frame-alloc-benchmark
CAP_GPUI_AUTO_EDITOR=<copy of reference-recording.cap> CAP_GPUI_AUTO_PLAYBACK=20 \
  CAP_GPUI_NO_MENUS=1 CAP_GPUI_NO_TRAY=1 CAP_GPUI_MUTE_AUDIO=1 cargo run --release

Related

Only Rust allocations change. FFmpeg, D3D/wgpu and the OS keep their own heaps.

The Windows heap serves blocks past its large-block threshold straight from
VirtualAlloc and frees them back to the OS. On Windows the editor preview
allocates two frame-sized buffers per frame (the RGBA readback and the
frame_image BGRA copy), so playback mapped, zero-faulted and unmapped a
full frame of pages twice per frame. mimalloc reuses its freed segments.

Adds examples/frame-alloc-benchmark.rs, which runs that per-frame buffer
pattern under both allocators.
@vercel

vercel Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

@divik-05 is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

@socket-security

socket-security Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​mimalloc@​0.1.5210010093100100

View full report

@diiviikk5
diiviikk5 marked this pull request as ready for review October 26, 2026 15:58
assert!(!readback.is_null() && !converted.is_null());
readback.write_bytes(0x40, padded.size());
converted.copy_from_nonoverlapping(readback, tight.size());
black_box(converted.read_volatile());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Benchmark can skip buffer work

Only one byte reaches black_box. read_volatile() preserves that byte's read, not the writes and copy across the remaining megabytes. Release builds could therefore report better timings and fewer page faults because the compiler skips work, not because the allocator improved. Pass the complete initialized output slice to black_box before freeing the buffers.

Suggested change
black_box(converted.read_volatile());
black_box(std::slice::from_raw_parts(converted, tight.size()));
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/examples/frame-alloc-benchmark.rs
Line: 33

Comment:
**Benchmark can skip buffer work**

Only one byte reaches `black_box`. `read_volatile()` preserves that byte's read, not the writes and copy across the remaining megabytes. Release builds could therefore report better timings and fewer page faults because the compiler skips work, not because the allocator improved. Pass the complete initialized output slice to `black_box` before freeing the buffers.

```suggestion
            black_box(std::slice::from_raw_parts(converted, tight.size()));
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

…-alloc-benchmark

Passing one byte to black_box left the optimizer free to drop the fill and
copy of the rest of the buffer. Hand it the full slice instead.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants