perf(tui): bound live thinking stream rendering to O(1) tail - #118
Conversation
- Bound active thinking stream rendering to a constant-depth tail buffer (max 24 lines / 4096 chars) - Avoid re-parsing the entire accumulated reasoning trace as Markdown on every delta - Preserve the complete reasoning trace for expansion (Ctrl+O) and full-text search - Protect surrogate pair boundaries during tail trimming - Add unit tests for live tail bounding, full trace retrieval, and UTF-16 safety
Adds scripts/bench-tui-thinking.ts (bun run bench:tui-thinking) with three modes for issue #117: - default: compares HEAD vs pre-fix ThinkingView (7f3d73b) on the same corpus - --verify: flatness gates (last bucket vs first bucket headroom, absolute per-frame ceiling, total ceiling) usable as a regression gate - --stress: full 100k-line stream through HEAD with CPU/RSS/heap deltas, plus geometric checkpoint probes on the legacy component to extrapolate its quadratic end-of-stream frame cost
Benchmark evidence for issue #117Added
1. Compare mode — reproduces the reported O(n²)Same corpus (mixed CJK prose, bold/code, fenced TS blocks, lists), same machine:
At ~50 KB (the issue's "saturation" point) the legacy component is already at ~168 ms/frame (~6 fps). At 109 KB it hits 625 ms/frame. 2. Stress mode — 100k-line stream (5.6 MB trace)HEAD completes the full 100,000-line stream:
Legacy cannot complete 100k lines — extrapolated to ~8.2 hours. Instead it is probed at 7 geometric checkpoints (each in a fresh view so total benchmark time stays linear):
Linear regression slope 4.927 ms/line, R² ≈ 1 (every doubling of trace → ~4× per-frame cost, confirming O(n²)). Extrapolated end-of-stream frame:
3.
|
| metric | legacy (7f3d73b) | HEAD (3920d74) |
|---|---|---|
| per-frame cost vs trace length | O(n) → O(n²) total | O(1) per frame |
| 100k-line end-of-stream frame | ~493,000 ms (extrapolated) | 0.344 ms |
| 100k-line total render | ~8.2 hours (extrapolated) | 34.5 s |
| speedup at 100k lines | — | ~856× |
| tail-buffer memory ceiling | unbounded trace in Markdown | 24 lines / 4 KB cap |
| full trace retained for search/expand | yes | yes (this.text + getSearchText()) |
The numbers reproduce the issue's reported "25% CPU / 594 MB / decaying throughput on a 50 KB thinking trace" and confirm the fix keeps active-stream rendering bounded regardless of trace length.
Fixes #117