Skip to content

gpui: lay out with Taffy 0.14, whose cache keeps clean subtrees laid out - #49

Merged
sunli829 merged 1 commit into
mainfrom
taffy-0.14
Oct 9, 2026
Merged

sunli829 merged 1 commit into
mainfrom
taffy-0.14

Conversation

@sunli829

@sunli829 sunli829 commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

What

Lay out with Taffy 0.14 instead of 0.13.

Taffy 0.13 caches one size per node per kind of constraint (e.g. "width known, height not"). When a node is measured under two different widths in one layout, the second evicts the first, and on the next frame both miss again. So a single dirty node makes Taffy lay out every clean subtree around it again, on every frame. In gpui_perf's settings-toggle, 8 dirty nodes out of 1203 cost ~2950 measure calls, ~27k cache misses and 3.3 ms of layout per frame. gpui-fast's retained layout already keeps nodes clean; this lets their caches actually hit. Taffy 0.14 keeps nine sizes per node, evicting the least recently used.

0.14 hands a leaf's measure function its whole layout. fast::layout::MeasuredTaffy lays each leaf out around GPUI's measurement as 0.13 did, so taffy.rs changes one line. docs/upstream-sync.md records the difference from upstream (still on 0.13).

Correctness

  • cargo test -p gpui --features test-support --lib: 641 passed.
  • gpui_perf --headless --verify: all 55 checks identical.
  • Painted output of every headless scenario (7644 frames, every quad, glyph, icon and image with bounds, clip and color) dumped from a 0.13 build and a 0.14 build: byte-identical.
  • script/check-upstream: 0 violations.

Performance

Headless, main-thread instructions per frame, retained views on:

Scenario Change
settings-tree-expand −80%
settings-toggle −60%
form-typing / form-validation −45%
table-ticks-few −41%
table-sort −34%
table-ticks-many −16%
layout-text / layout-panel −16%
workspace-* about −5%
settings-status-tick / settings-theme-switch +3% / +2% (no layout to save; 0.14 scans more cache entries)

Real window (showcase --auto, median of 3 alternating runs): Taffy's share of the workspace scenarios drops by ~40% (e.g. 0.34 → 0.20 ms). A settings page of 80 row views, measured in a real window with a temporary showcase page that is not part of this PR, went from 7–8 ms to 3–4 ms of main-thread CPU per frame (layout 5–6.5 ms → 2–2.4 ms).

🤖 Generated with Claude Code

Taffy 0.13 keeps one cached size per kind of constraint, so a node measured
under two widths in one layout evicts one with the other. A single dirty node
then lays out every clean subtree around it again, on every frame: in
gpui_perf's settings-toggle, eight dirty nodes out of 1203 cost ~2950 measure
calls and 3.3 ms of layout. Taffy 0.14 keeps nine sizes per node whatever their
kind.

0.14 hands a leaf's measure function its whole layout; fast::layout's
MeasuredTaffy lays each leaf out around GPUI's measurement as 0.13 did, so
taffy.rs only changes the tree it lays out through.
@sunli829
sunli829 merged commit 412191f into main Oct 9, 2026
4 checks passed
@sunli829
sunli829 deleted the taffy-0.14 branch October 9, 2026 06:33
trancong12102 added a commit to aislopware/gpui-fast that referenced this pull request Oct 9, 2026
Takes longbridge#44 (chat transcript lists stay composited on their scroll layers),
longbridge#46 (layout panics and stale rendering in view splices), longbridge#47 (caret
sibling requirements in releases) and longbridge#49 (Taffy 0.14 with MeasuredTaffy).
Leaves out longbridge#41, longbridge#42 and longbridge#43, upstream's window composition for X11 and
wgpu, as the fork keeps its own composition (as in ed16b23).

Conflicts:
- Cargo.lock: ours, re-resolved for taffy 0.14 (no gtk/wry).
- docs/upstream-sync.md: ours plus upstream's Taffy 0.14 entry; the
  composition entry stays out.
- gpui_perf/Cargo.toml: ours (no linux_webview, no webview deps).
- gpui_perf/src/runner.rs: both scroll scenario lists.
- gpui_macos/src/fast/composition.rs, gpui_linux x11/window.rs and
  client.rs, the three composition.rs files, README, ci.yml: ours.
- fast/dependencies.rs: ours (interned, sorted states, Checked) with longbridge#44's
  shared read logs and dependencies_so_far, its states interned as ours.
- fast/layers/invalidate.rs: longbridge#44's OwnerChange and render_dependencies;
  with_own_rebuild keeps our rebuild ranges and note_rebuild guard and
  falls back on longbridge#44's rendering_since.
- fast/layers/lists.rs: both row fields (fades, reads); our row scene
  insertion; drawn_alike over fast::scene::operations.
- fast/layout.rs: both retention fields (root_phases; pending_replays,
  list_rows).
- fast/layout_key.rs: longbridge#44's list row hooks with our fast_retention().
- fast/retained.rs: both state fields; note_rebuild and note_rendering
  both called; recording.rendered before our place_root.
- fast/splice.rs: ours (splice_gaps, deferred_out_of_date, kept_layout,
  Checked) with longbridge#46's layout_reaches guard and offset-read checks for the
  view, its gaps and its deferred draws; render_dependencies copied.
- fast/tests/retained.rs: both sides' tests.
- fast/tests/layers_lists.rs: our outside-write test now checks the
  frame draws as without layers (under longbridge#44 the rows render again on a
  composited frame instead of demoting).
- gpui_perf chat.rs: momentum_phase for our ScrollWheelEvent.

cargo test -p gpui --features test-support --lib: 764 passed.
gpui_perf --headless --verify: 72 checks identical.
settings-toggle (retained): 61.75M -> 23.60M instructions per frame,
1735 -> 25 measure calls, layout 4.2 ms -> 1.5 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant