perf(cli): reduce virtualized history scroll latency - #10043
perf(cli): reduce virtualized history scroll latency#10043DragonnZhang wants to merge 5 commits into
Conversation
E2E / PTY test reportTested on macOS with a real 100x30 PTY using the same history workload and 80-event oscillating SGR wheel sequence for the global 0.22.0 baseline and the locally built candidate.
Additional validation:
The Inspector capture attributed roughly 35 ms per emitted frame to Ink/Yoga layout, composition, ANSI diffing, and terminal output. Mouse parsing was negligible. A separate O(item-count) offsets rebuild was observed only in very large synthetic resumed histories and is intentionally outside this PR. |
|
Thanks for the PR!
Moving on to code review. 🔍 中文说明感谢贡献!
进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
|
Code review Read the diff against the current
No Critical issues, no AGENTS.md violations. Testing evidence — this PR's CI (fetched via API; per gate rules I do not run PR code) At the time of this pass, the three platform unit-test lanes are still running on the reviewed commit; security, precheck, and desktop-shell checks are green, and the conditional jobs (integration, tmux, verify) are skipped pending their triggers. Not verified: the live latency/FPS improvement — the unit suite pins the scheduling semantics, not the end-to-end timing win, and the before/after numbers in the PR description are the author's own macOS PTY measurements. I'm not treating them as independent evidence. Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Sandboxed verification would settle the remaining claim: the live responsiveness win (29 ms → 11 ms first output, 18.7 → 31.7 FPS) is currently the author's measurement on macOS only, and a green suite can't substantiate it — 中文说明代码审查 对照当前
无 Critical 问题,无 AGENTS.md 违规。 测试证据 —— 本 PR 自身的 CI(通过 API 获取;按门禁规则不运行 PR 代码) 截至本次审查,三个平台的单元测试通道仍在评审中的提交上运行;安全扫描、预检和桌面 shell 检查已通过,条件触发的任务(集成、tmux、verify)处于跳过状态等待触发。未验证:真实的延迟/帧率提升——单元测试固定的是调度语义,不是端到端的时间收益;PR 描述中的 before/after 数字是作者本人在 macOS 上的 PTY 测量结果,不作为独立证据引用。 CI 明细见上方表格(终审工作流会在 CI 结束后原地更新该表)。 沙箱验证可以了结剩余的声明:真实的响应速度提升(首次输出 29 ms → 11 ms、帧率 18.7 → 31.7)目前仅是作者在 macOS 上的测量,绿色测试套件无法证实—— — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean, minimal, evidence-grounded change; the only reservation is that the size of the latency/FPS win rests on the author's single-platform measurement, and CI was still running at review time. Honest read: this is a well-constructed PR. Before reading the diff, the obvious fix for "trailing-only scheduler adds a fixed 16 ms on top of a 35 ms frame" was leading-edge flush plus remaining-deadline scheduling — which is exactly what landed, in 25 production lines on the existing hook, with tests that pin each new behavior and a design doc that records the profiling evidence and explicit non-goals. No simpler path was missed, nothing unrelated rode along, and the invariants that matter (once-per-frame cap, drag precedence, scrollbar cancellation, preserved wheel deltas) are all kept and tested. The problem is real and measured, not theoretical: a fixed scheduling delay stacked on an already-expensive frame, with before/after captures whose numbers are internally consistent with the mechanism (removing one 16 ms wait from a ~51 ms per-frame path lands close to the reported 29 ms → 11 ms first output and ~2× frame-rate region). The one thing I can't independently confirm from here is the magnitude of the win on real terminals — that's the author's macOS PTY data, and Windows/Linux were declared out of scope; the CI unit suite settles the scheduling semantics on all three platforms but not the end-to-end timing. That gap is named in the Stage 2 comment together with the sandboxed lanes that would close it, and it's a reservation, not a blocker: the code change is safe and correct on its own terms. Verdict: approve. CI is still running on the reviewed commit, so approval is deferred until CI lands green on 中文说明信心度:4/5 —— 干净、最小、有证据支撑的改动;唯一的保留意见是延迟/帧率提升的幅度目前只有作者单平台的测量数据,且审查时 CI 仍在运行。 直接说:这是一个结构良好的 PR。在读 diff 之前,针对"仅尾沿调度器在 35 ms 帧之上再叠加固定 16 ms"的显而易见修法就是前沿立即执行加剩余截止时间调度——实际落地的正是这个方案:25 行生产代码改在现有 hook 上,测试固定了每一种新行为,设计文档记录了性能分析证据和明确的非目标。没有错过更简路径,没有夹带无关改动,关键不变量(每帧一次上限、拖动优先、滚动条取消、滚轮增量保留)全部保留且有测试覆盖。 问题真实且经过测量,不是理论问题:在已经很昂贵的帧之上叠加了固定调度延迟,before/after 数据与机理自洽(从约 51 ms 的每帧路径中移除一次 16 ms 等待,与报告的 29 ms → 11 ms 首次输出和约 2 倍帧率区间吻合)。我无法从这里独立确认的只有真实终端上收益的幅度——那是作者在 macOS 上的 PTY 数据,Windows/Linux 声明为范围外;CI 单元测试在三个平台上固定了调度语义,但不覆盖端到端耗时。这个缺口已在 Stage 2 评论中与可以关闭它的沙箱通道一并指出;它是保留意见而非阻塞项:就代码改动本身而言是安全且正确的。 结论:批准。CI 仍在评审中的提交上运行,因此批准推迟到 CI 在该提交上全部变绿之后。 — Qwen Code · qwen3.8-max Reviewed at |
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
tmux E2E test report (head
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
中文说明
已审查——无阻断问题。 建议见行内评论。
— qwen3.8-max via Qwen Code /review (v0.22.0)
Address review feedback on QwenLM#10043: - Add a ScrollableList mouse test that writes a single SGR wheel event to stdin and asserts the viewport moved exactly WHEEL_LINES_PER_TICK before any frame timer fires. This pins the PR's headline immediate first-wheel behavior through the real stdin -> handler -> schedule() pipeline; previously immediacy was only covered at the hook unit level, so a regression re-introducing pre-flush delay in the mouse path would have passed the whole component suite. - Add a useFrameCoalescedFlush test that schedules twice (leading applies, trailing pending), unmounts, advances timers past the deadline, and expects exactly one flush. Guards the documented 'timer is always cleared on unmount' contract; deleting the cleanup effect was previously a surviving mutant.
…acing coalescer decision Address review feedback on QwenLM#10043: - Label all performance figures in the new scheduling doc as single-machine observations (one macOS host, 100x30 real PTY, Node.js 22 runtime) that have not been independently reproduced, and note the capture scripts are not part of the tree. - Record the before/after PTY comparison the Verification section previously described without results: capture environment plus the measured table (first-output 29.01 -> 11.11 ms, sustained 18.71 -> 31.75 FPS, synchronized frames 16 -> 25, wheel distance 3 rows). - Add a 'Relationship to the frame pacing doc' section that cites docs/design/2026-08-21-vp-scroll-frame-pacing.md and explains why its leading-and-trailing rejection no longer holds: the scheduler changes when the first application happens, not how far a burst scrolls — accumulated delta preservation is guarded by the burst-preservation unit test (exact 90-row delta) and the real-PTY row-delta capture (239/240 rows). - Mark the pacing doc's trailing-coalescer bullet as superseded and point to the new doc so only one authoritative statement on VP scroll input coalescing remains.
|
@qwen-code /triage — CI on the current head is fully green now (the earlier deferred-approval hold cited a run that has since landed green); re-running triage for the deferred approval. |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 212 passed · 0 failed · 212 total Flakiness gate: ✅ 2 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:212 通过 · 0 失败 · 212 总计 抖动门:✅ 2 changed test file(s) x 5 identical rounds, no divergence Verification reportPR #10043 — perf(cli): reduce virtualized history scroll latencyVerdict: 中文摘要
Scope selection
Changed surface is one 25-line hook ( Central claim — A/B load-bearing proofControl:
The head/base flip on every leading-edge cell (0 vs 16 ms at the component level; synchronous vs deferred flush at the hook level) while the semantics cells (delta preservation, cancellation, convergence) agree on both arms — the change is load-bearing and behavior-preserving. This is the mechanism-level analog of the PR's real-PTY "first visible output 29.01 → 11.11 ms" row; the scheduling delay removed is exactly one 16 ms frame wait. Secondary claims verified at the hook level with the PR's own fake-timer tests (6/6 green) plus the real-timer cells above: the deadline test's pass also proves Reviewer Test Plan walkthrough
Mutation matrix (PR's new tests vs mutants of the changed file)Each mutant applied to the head tree, focused suites (
Zero survivors. Every guard the PR introduces is pinned by a test that fails with the intended behavioral assertion when the guard is removed. M1 doubles as the vacuity check for the whole PR. FindingsNone blocking. Non-blocking observations (completeness reporting, not merge conditions):
Not covered
MethodologyEnvironment: Flakiness gate logEvidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
|
CI note: the only failing check is the same pre-existing main-side failure as #9682/#8332 — 中文说明:唯一失败为主干预存的 classify-release-notes 辅助测试问题(本分支未改动相关文件),其余检查全绿,与最新 main 的合并无冲突。 |
|
@qwen-code /review |
|
@qwen-code /review |
|
Qwen Code review request accepted. Review is running in workflow run. A command-triggered review is not listed under the checks of this PR; the result is posted here as a review when it finishes. |




What this PR does
This change makes Virtualized History scroll scheduling leading-edge and deadline-aware. The first wheel or drag update now applies immediately, later updates within the same 16 ms window remain coalesced, and an update arriving after an over-budget render no longer waits through another full frame interval. Accumulated wheel deltas and scrollbar cancellation semantics remain unchanged.
It also adds focused coverage for immediate leading updates, remaining-deadline scheduling, over-budget scheduling, burst coalescing, and cancellation, and records the profiling evidence and non-goals behind the change.
Why it's needed
Real PTY profiling showed that the input parser was inexpensive, while each emitted Ink frame spent about 35 ms in layout, composition, ANSI diffing, and terminal output. The previous trailing-only scheduler added a fresh 16 ms delay on top of that work, causing sluggish first response and low visible frame rate even though wheel deltas were preserved.
Removing the avoidable scheduling delay improves responsiveness without introducing a larger rendering or indexing rewrite.
Reviewer Test Plan
How to verify
Enable Virtualized History, open a session with enough history to scroll, and send a sustained burst of SGR wheel events. Confirm that the first wheel event updates the viewport immediately, subsequent events are coalesced without losing their accumulated row delta, and clicking the scrollbar cancels any pending trailing wheel update.
Run the focused CLI tests for the frame coalescer, scrollable list, and virtualized list. Confirm all tests pass, then run the repository build and typecheck.
Evidence (Before & After)
The same 100x30 real-PTY workload and 80-event oscillating wheel sequence were used for both captures. The change reduced first-output latency by about 61.7% and increased visible frame rate by about 69.7% while retaining the terminal row delta.
Tested on
Environment (optional)
macOS real PTY at 100x30, Node.js 22-compatible workspace runtime, global Qwen Code 0.22.0 baseline, and the locally built candidate. Validation also included 52 focused Vitest tests, focused ESLint, the full repository build, and the full repository typecheck.
Risk & Scope
ui.useTerminalBufferand restart.Linked Issues
N/A
中文说明
本 PR 做了什么
此更改让 Virtualized History 的滚动调度同时支持前沿立即执行和基于帧截止时间的调度。第一个滚轮或拖动更新现在会立即生效,同一个 16 ms 窗口内的后续更新仍会合并;当一次渲染已经超过帧预算时,后续更新也不再额外等待完整的一帧。滚轮增量累积和滚动条取消语义保持不变。
同时新增了针对首次立即更新、剩余截止时间调度、超预算调度、突发事件合并和取消行为的集中测试,并记录了本次修改所依据的性能分析证据和非目标。
为什么需要它
真实 PTY 性能分析表明,输入解析开销很小,而 Ink 每个可见输出帧会在布局、组合、ANSI 差异计算和终端输出上花费约 35 ms。之前仅尾沿执行的调度器还会在这些工作之外新增完整的 16 ms 等待,因此即使滚轮增量没有丢失,首次响应仍然迟缓,可见帧率也较低。
移除这段可避免的调度等待可以提升响应速度,同时不需要引入更大范围的渲染或索引结构重写。
Reviewer Test Plan
如何验证
启用 Virtualized History,打开一段足够长、可以滚动的会话历史,然后持续发送一组 SGR 滚轮事件。确认第一个滚轮事件会立即更新视口,后续事件会被合并但不会丢失累计行数,并且点击滚动条会取消仍在等待的尾沿滚轮更新。
运行帧合并器、可滚动列表和虚拟列表相关的 CLI 集中测试,确认全部通过,然后运行仓库全量构建和类型检查。
证据(修改前后)
两次采集使用相同的 100x30 真实 PTY 工作负载和包含 80 个事件的往返滚轮序列。此更改将首次输出延迟降低约 61.7%,将可见帧率提升约 69.7%,同时完整保留终端行增量。
测试平台
环境(可选)
macOS 真实 PTY(100x30)、兼容 Node.js 22 的工作区运行时、全局 Qwen Code 0.22.0 基线版本和本地构建的候选版本。验证还包括 52 个集中 Vitest 测试、集中 ESLint、仓库全量构建和仓库全量类型检查。
风险与范围
ui.useTerminalBuffer并重启。关联 Issue
无