perf(shell): skip reading unchanged rows when rendering#2918
perf(shell): skip reading unchanged rows when rendering#2918domenkozar wants to merge 2 commits into
Conversation
devenv shell mediated all PTY output through a libghostty VT and re-read every cell of every row on each frame, so rendering cost scaled with screen area. Nested full-screen TUIs (neovim, claude-code) that emit frequent updates triggered full-screen re-reads even for single-line changes, causing noticeable lag on larger terminals. The renderer now tracks per-row dirty state via a libghostty RenderState: clean rows that already have a baseline are skipped without reading their cells, and the screen's dirty flags are consumed after each frame so only rows changed since then are re-read. When the render state can't be allocated it falls back to reading every row. Output is byte-identical; this only avoids redundant per-cell FFI reads. Also bumps the PTY read buffer from 4KB to 64KB so a full-screen repaint burst feeds the VT as one frame instead of fragmenting across syscalls. Adds a rows_read counter and deterministic unit tests asserting a single-line repaint reads only a handful of rows, and an unchanged frame reads none. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploying devenv with
|
| Latest commit: |
0e29c58
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://5411dea0.devenv.pages.dev |
| Branch Preview URL: | https://fix-shell-dirty-row-renderin.devenv.pages.dev |
|
I'm still not happy with this, claude scrollbar is slow |
|
I took this branch for a test drive, it does feel better, but really starts to struggle as soon as I hit the bounds of a pane and some kind of scroll happens. I owe you a video as it's more clear what kind of renders it struggles with watching a session. It's definitely not to the point that I'd willingly run nvim or any other TUI within the devenv TUI itself, there's a perfectly fine workaround with I appreciate you burning some tokens for my sake. I can also live with just using devenv a bit differently than I have in the past. |
|
I want to fix this before we get 2.2 out. |
Summary
devenv shellmediates all PTY output through a libghostty VT and previously re-read every cell of every row on each frame, so rendering cost scaled with screen area. Nested full-screen TUIs (neovim, claude-code) that emit frequent updates triggered full-screen re-reads even for single-line changes, causing noticeable lag on larger terminals.The renderer now tracks per-row dirty state via a libghostty
RenderState:Output is byte-identical; this only avoids redundant per-cell FFI reads.
Also bumps the PTY read buffer from 4 KB to 64 KB so a full-screen repaint burst feeds the VT as one frame instead of fragmenting across syscalls.
Correctness note
The optimization relies on libghostty's per-row dirty flags being a superset of all visible changes. This holds because a global
Dirty::Full(palette / reverse-video / scroll-region / default-color changes) forces every per-row bit dirty in ghostty's render-state update, so reading only the per-row flags is safe. A viewport scroll marks dirtiness at the page level, which the render-state snapshot reflects (the bare per-row bit would miss it) — covered by a regression test.Tests
Deterministic unit tests (no timing):
rows_read, so it fails if the page-level dirty path regresses — not just a substring check).🤖 Generated with Claude Code