screen-clone was 2.6x slower than Zig. The main cost was in
PageList::clone_range: it created a throwaway page, recycled it into the
pool and Page::reinit then memset the entire page again, faulting in
every cell. Untouched pages now go to a clean_pool that create_node
reuses without re-zeroing. Page::init allocates cells with
bytemuck::allocation::try_zeroed_vec (Cell derives Zeroable), getting
lazily-zeroed memory from the allocator without unsafe code, and
clone_partial_row_from bulk-copies plain-cell runs in rows with managed
memory. screen-clone clone: 78 ms -> ~28 ms (Zig ~27.8 ms).
osc::Parser::next_slice appends payload bytes in bulk while behaving
exactly like per-byte next() (2048 limit, overflow), verified by a
property test; the osc-parser bench uses it (54.5 -> ~32 ms, Zig ~28).
Still slower: screen-clone render (~2.4x), dominated by per-row style
vectors in RenderState::update.
Test Plan:
- cargo test --workspace (no failures)
- ghostty-bench +screen-clone / +osc-parser with --profile production
Adds crates/ghostty-bench, a port of ZIG/src/benchmark and src/synthetic:
the ghostty-bench binary (terminal-stream, terminal-parser,
codepoint-width, grapheme-break, is-symbol, osc-parser, screen-clone
with Zig's CLI) and ghostty-synthetic data generators (+ascii, +utf8,
+osc, plus a Rust-only +sgr mode).
Measured against Ghostty's own ghostty-bench built with Zig 0.15.2
(ReleaseFast) vs --profile production, the hot paths were optimized
without behaviour changes:
- Terminal::print single-lookup fast path for width-1 chars mid-line
- page grapheme allocator: bitmap first-fit instead of a linear scan
(was 38% of utf8 stream time; randomized equivalence test added)
- plain cells skip per-cell work in clear_cells and row cloning
- ASCII fast path + strict hand-written UTF-8 decoder (exhaustive
equivalence test against std)
- parser/OSC parser split into inlined fast and state-change paths
- RenderState allocates grapheme vectors only for grapheme rows
- Page::assert_integrity no-op stub so the production profile builds
terminal-stream is now at parity (ascii 1.08x, utf8 0.97x, SGR 1.04x of
Zig's time). Still slower: screen-clone (~2.6x, zero-filling fresh
cell Vecs vs Zig's lazily-zeroed mmap), osc-parser (1.9x), and the raw
parser on SGR data (1.5x). Full table in docs/rewrite/notes/perf-P1.md.
Test Plan:
- cargo test --workspace (no failures)
- cargo run --profile production -p ghostty-bench -- +terminal-stream
--data=<file> and compare with Zig's ghostty-bench
Integration pass over the terminal core (Codex/gpt-6-astra, reviewed):
- Screen now owns the real kitty ImageStorage; new/reset/teardown,
erase-display deletes and alternate-screen limit inheritance hook in
where Zig does, and Terminal implements ExecTerminal so kitty APC
commands execute against it. Exec tests use a real Terminal.
- The PageList gains the APIs Screen/Terminal needed (pin iterators,
cross-page row/partial-row clone, string allocator probing, dedup
before allocating hyperlinks, Top viewport fixup after erase, append_
grapheme no longer marking rows dirty), and the Screen/Terminal
workarounds for their absence are removed so the code mirrors Zig.
Public signatures are unchanged; downstream crates build as before.
Test Plan:
- cargo test -p ghostty-terminal (1252 passed, 0 ignored)
- cargo clippy -p ghostty-terminal --all-targets -- -D warnings
Second pass over the page memory model (done by Codex/gpt-6-astra in an
isolated worktree, reviewed and merged here):
- Resize/reflow now port Zig's incremental ReflowCursor algorithm with
page backfill and the same capacity growth/split sequence, instead of
rebuilding a snapshot, so page boundaries, pruning and tracked-pin
remapping match Ghostty.
- Point<->pin conversion for the active area starts at the list tail and
the viewport offset is cached, so hot-path operations no longer walk
the whole scrollback from the head.
- RefCountedSet reproduces Zig's observable ID reuse and error points.
- All 202 PageList tests are ported (plus 13 extra regressions).
With this, the five Screen/Terminal tests previously ignored as blocked
on PageList behaviour pass and are re-enabled.
Test Plan:
- cargo test -p ghostty-terminal (913 passed, 0 ignored)
- cargo clippy -p ghostty-terminal --all-targets -- -D warnings
First cut of the terminal core memory model from ZIG/src/terminal:
size, point, style, hyperlink, ref_counted_set, page and PageList.
Design (see docs/rewrite/ARCHITECTURE.md decisions 2-4):
- Cell/Row are 8-byte u64 wrappers with Zig's exact bit offsets.
- Page owns row/cell Vecs plus style set, grapheme and hyperlink maps;
Page::layout reproduces Ghostty's byte-size formula on 64-bit Linux so
scrollback-limit pruning matches.
- PageList keeps nodes in a generation-checked slab with a free list and
list-owned tracked pins addressed by handles.
This is explicitly partial (docs/rewrite/notes/ghostty-terminal-
pagelist.md): 151/202 PageList tests ported; reflow rebuilds a snapshot
instead of Zig's incremental ReflowCursor; point<->pin conversion walks
from the list head (no active/viewport caches); RefCountedSet doesn't
reproduce probe-dependent ID reuse. A follow-up pass finishes parity.
Test Plan:
- cargo test -p ghostty-terminal (239 tests)