Skip to content

perf 1/5: benchmark harness, TOASTTY_STATS counters and baseline - #61

Merged
EDToaster merged 4 commits into
mainfrom
perf-experiment
Oct 22, 2026
Merged

EDToaster merged 4 commits into
mainfrom
perf-experiment

Conversation

@EDToaster

Copy link
Copy Markdown
Owner

Stack 1/5 of the performance programme. It adds the measurement tooling the rest of the stack is judged by, and records a baseline. The PRs above this one each fix one area, and each one's body shows its own before/after.

What's here

  • TOASTTY_STATS=<path> (crates/toastty/src/perf_stats.rs). When it's set, the binary counts frames, skipped frames and PTY bytes/events, and times parsing and render_term. It also records output→frame and key→frame latency, utime/stime and VmHWM/VmRSS, and writes all of it as JSON when the PTY closes. When it's unset, every hook is a branch on None.
  • bench/run.py. 13 scenarios on a private Xvfb display, with seeded workloads cached in --work/data: startup, idle CPU (blinking and steady), cat floods (ASCII, SGR colour, Unicode/CJK/emoji, seq), vtebench-style dense_cells at 128×42 and 256×73, scrollback memory, key latency, and wheel-scroll frame time at two window sizes. It follows the AGENTS.md safety rules: an explicit free display ≥ 99, throwaway config, HISTFILE=/dev/null, and it only kills PIDs it started.
  • bench/compare.py. Turns result files into a Markdown before/after table.
  • examples/parse_bench.rs. Headless parser + Term throughput, with no window or GPU.
  • The pprof cargo feature (off by default) and bench/profile.sh / bench/folded.py. An in-process SIGPROF sampler for machines where perf/samply aren't allowed (perf_event_paranoid=2).
  • bench/README.md. How to use all of the above.

Baseline (main d29928d, Ryzen 7 3700X + RX 7700 XT, Xvfb + MESA_VK_WSI_DEBUG=sw, median of 3)

scenario result
startup launch→first frame 124 ms, idle RSS 101 MB
idle 0.2 % CPU with a blinking cursor, 0.0 % steady
cat 64 MB ASCII 34 MB/s, 0 frames drawn while streaming, peak RSS 417 MB
cat 32 MB SGR 50 MB/s, 0 frames, peak RSS 197 MB
cat 16 MB Unicode 24 MB/s, 0 frames, peak RSS 196 MB
seq 3M lines 13 MB/s, 0 frames, peak RSS 652 MB
dense_cells 128×42 / 256×73 109 / 100 MB/s, 0 frames
key→frame p50 0.64 ms, p99 3.2 ms
wheel-scroll frame p50 1.2 / p99 4.2 ms (2560×1400: 2.1 / 5.1 ms)
headless parse ASCII 44, SGR 62, Unicode 29, dense 114, seq 20 MB/s
binary 25 MB stripped

What the baseline shows:

  • Nothing is drawn during a flood. The PTY reader posts every 4 KiB read as its own winit user event over an unbounded channel. The main thread never gets to RedrawRequested until the flood is over, and the backlog sits in memory meanwhile.
  • Parsing is slow per character and per line. Each printed char pays for per-column damage bookkeeping. Each linefeed blanks a whole recycled row.

The rest of the stack addresses both.

Visual evidence

There's no visual change. This is the instrumented build with TOASTTY_STATS set, drawing colour, CJK, emoji and a combining mark as usual:

TOASTTY_STATS build rendering normally

Tests

cargo test -p toastty: 302 passed. Clippy adds no warnings in the new files (--features pprof).

@EDToaster
EDToaster added this pull request to stack #66 October 22, 2026 18:08
@EDToaster
EDToaster merged commit 45784a7 into main Oct 22, 2026
2 checks passed
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