Skip to content

perf(render): skip unchanged rows in the daemon render hot path - #25

Merged
marcrdgz merged 3 commits into
mainfrom
perf/render-hot-path
Oct 19, 2026
Merged

marcrdgz merged 3 commits into
mainfrom
perf/render-hot-path

Conversation

@marcrdgz

Copy link
Copy Markdown
Owner

Plan section 2, items 2.1 and 2.2, plus a dirty-row bug found while measuring them.

Changes

  • Stop sampling agent status on every render (2.2). render_dirty computed AgentStatus on every frame of an AI pane, but its only caller (ui.rs) discarded it; the 500 ms refresh_agent_statuses tick was already the sole consumer. render_dirty now returns (), so detection timing is unchanged.
  • Clear the dirty flags of every redrawn row (bug). vt.clear_dirty walks rows with a peekable cursor and assumes ascending order, but render_dirty passed HashSet order, so most rows were never cleared and every later frame redrew them. That largely cancelled d9ed455 ("skip unchanged rows"). The fix sorts the rows and adds a debug_assert! on the contract.
  • Scan composer rows only where needed (2.1).
    • Non-Codex panes now scan only the rows they are about to redraw instead of reading every cell over FFI each frame. A placeholder that appears is always on one of those rows.
    • Finding a placeholder there switches that frame to a full-viewport scan, so the composer is complete on the same frame.
    • The placeholder row is remembered until a full scan proves it is gone.
    • Codex panes and full redraws keep the full scan, so composer behavior is unchanged.

Benchmarks

Release build, --test-threads=1, µs per render, 120x40 viewport with one echoed key per frame. The benchmark now starts from a full screen of text, and an AI-pane variant was added.

shell pane AI pane
baseline (aa5c6fe) 328–362 580–1245
+ agent status 315–377 312–422
+ dirty-flag fix 296–323 274–311
+ composer scan 25–27 27
rebased on main (with #23) 22 18

benchmark_echo_render_cost (client only) is unchanged.

Tests

  • Regression tests:
    • render_clears_every_redrawn_row
    • codex_placeholder_on_partial_frame_fills_clean_composer_rows
    • codex_placeholder_is_remembered_until_it_disappears
    • agent_status_does_not_depend_on_render
  • Each of the first three was checked to fail with its safeguard removed.
  • Gate is green after rebasing on main: cargo build, test and clippy --workspace.
  • Manually checked shell, vim, Codex, Claude and opencode panes.

Risk

Because of the dirty-flag fix, unchanged rows are now really skipped. Any place where Ghostty fails to mark a changed row dirty would now leave stale content, where the full redraws used to hide it.

@marcrdgz
marcrdgz merged commit e4645d4 into main Oct 19, 2026
2 checks passed
@marcrdgz
marcrdgz deleted the perf/render-hot-path branch October 19, 2026 20:38
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