Skip to content

tui: the published bench — both sides through a terminal reducer, and only what drew the same screen is a number; step 6 complete - #405

Merged
shreeve merged 5 commits into
mainfrom
tui/bench
Sep 22, 2026
Merged

shreeve merged 5 commits into
mainfrom
tui/bench

Conversation

@shreeve

@shreeve shreeve commented Sep 22, 2026

Copy link
Copy Markdown
Owner

PLAN step 6, the bench half; with #404 this completes the plan's six steps.

The reducer. bench/harness.rip's Terminal replays what each side wrote — cursor moves, erases, SGR through test/ink/cells.rip's reader, line feeds and scrolling into a kept scrollback, the DEC modes, the alternate screen, grapheme widths, wraps, resizes in stream order — and refuses anything it does not model. After every update the two screens, scrollback included, are compared cell for cell, text and style (a blank shows no color; trailing blanks and trailing blank rows are dropped); Ink's five runs must agree with each other, ours too, and both sides with each other, or the scenario is refused with the first differing cell in its place. Reduction runs after the timing loops, so its cost is in neither side's number. Ink's update resolves on the write that ends its synchronized frame, so its throttle cannot misattribute one.

bun run bench runs every scenario PLAN §11 lists on both sides — the same tree, node for node — each in a fresh process, five times, and writes bench/RESULTS.md with the machine, versions and load; README's numbers and PLAN §11 quote it, and every number reproduces with bun run bench. CPU µs per update, median ±half the spread, Apple M5, Bun 1.4.2, Ink 7.1.1 on React 19.3.0's production build with memoized rows and its throttle lifted:

Scenario Ink Rip TUI Same screen
counter in a 1,000-element tree 3,638 18 ✓
40×8 table, 10% / 100% churn 3,413 / 4,241 216 / 343 ✓
2,000-row list, scroll by one 22,346 181 ✓
10,000-row list, scroll by one 105,608 303 ✓
insert at the top of a 50-row list 2,740 422 ✓
1,000 scrollback appends 334 147 ✓
resize 120 → 80 → 120 2,049 701 ✓
full relayout of 10,000 nodes 7,753 2,325 ✓
20×8 table of CJK and emoji 2,728 414 ✓

Cold start, median of 7: Ink imports in 39.5 ms and draws 70.6 ms from process start; Rip TUI 8.6 ms and 39.0 ms. Every Ink update is three writes; ours is one.

Where it does not flatter, stated in PLAN §11 and the README: Static appends are superlinear here (149 µs each over 1,000, 348 over 8,000) where Ink is flat, so past about 4,000 appends Ink wins — TODO §5 holds the cause; a resize writes more bytes than Ink (6,635 vs 4,295), since the frame is drawn from nothing; and a wrap divergence the reducer caught at a 33% cell (Ink wraps at Yoga's float width 39.6, we at the rounded 40, and our wrapper leaves a leading space when a line ends exactly at a space) is recorded as open text-engine work, and the scenario uses 25% cells that are whole at both widths.

Lines of code (bun run lines, non-blank non-comment by test/lines.rip's rule): Ink src/ 6,760 against 4,252 here (1.6×); with Yoga's algorithm, 10,252 against 4,252 (2.4×, of which layout.rip is 1,466). PLAN §2's "about 3×" does not hold and now says 2.4× and why: the package ships the mouse, hover, the kitty keyboard, selection, links and progress, which Ink does not.

Also: package.json ships mouse.rip, which it did not list; cells.rip exports its SGR reader for the reducer.

Suites unchanged (14, examples included); root style gate passes.

@shreeve
shreeve merged commit 2e06905 into main Sep 22, 2026
2 checks passed
@shreeve
shreeve deleted the tui/bench branch September 22, 2026 06:13
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