perf: close the last benchmark gaps to Zig (render state, parser)

- RenderState stores styles sparsely (per-cell u16 style ids plus one
  Style per distinct run) and skips plain cells with a single mask test
  (Cell::is_plain). screen-clone render: 11.5 ms -> 4.1 ms (Zig 4.7).
  row::never_extend_bg now takes a style accessor instead of a slice.
- Parser::next_with(byte, |action| ..) hands actions to a closure
  instead of moving a 64-byte action array out; Parser::next remains a
  wrapper over the same core (equivalence test added).
  terminal-parser SGR: 538 ms -> 231 ms (Zig 354).

All Ghostty benchmarks are now at or below Zig's time; table in
docs/rewrite/notes/perf-P1.md (the parser benchmark uses next_with with
a discarding closure, which like Zig's `_ = actions` lets the optimizer
skip unused work).

Test Plan:
- cargo test --workspace
- ghostty-bench +screen-clone --mode=render / +terminal-parser
This commit is contained in:
ddidderr committed 2026-10-12 05:22:01 +02:00
1 parent d89691b0ce
commit abb89a80e9
7 files changed
+177 -56

No files matched your search

+2 -2
View File
@@ -2283,7 +2283,7 @@ impl DrawState {
pe.up = !never_extend_bg(
row.raw,
cells_raw,
&cells.style,
|x| cells.style(x),
&colors.palette,
colors.background,
);
@@ -2293,7 +2293,7 @@ impl DrawState {
pe.down = !never_extend_bg(
row.raw,
cells_raw,
&cells.style,
|x| cells.style(x),
&colors.palette,
colors.background,
);
+17 -12
View File
@@ -10,12 +10,11 @@ use ghostty_vt::color::{Palette, Rgb};
/// color extended for filling padding space in the renderer. This is
/// a set of heuristics that help making our padding look better.
///
/// `styles` is the (possibly empty) style array of the render-state row;
/// when empty, every cell has the default style.
/// `style_of` returns the style of the render-state row's cell at `x`.
pub fn never_extend_bg(
row: Row,
cells: &[Cell],
styles: &[Style],
style_of: impl Fn(usize) -> Style,
palette: &Palette,
default_background: Rgb,
) -> bool {
@@ -34,7 +33,7 @@ pub fn never_extend_bg(
match cell.content_tag() {
// If it is a background color cell, we check the color.
ContentTag::BgColorPalette | ContentTag::BgColorRgb => {
let s = style_at(*cell, styles, x);
let s = style_at(*cell, &style_of, x);
let Some(bg) = s.bg(cell, palette) else {
return true;
};
@@ -60,7 +59,7 @@ pub fn never_extend_bg(
// A default background is applied if there is no background
// on the style or the explicitly set background
// matches our default background.
let s = style_at(*cell, styles, x);
let s = style_at(*cell, &style_of, x);
let Some(bg) = s.bg(cell, palette) else {
return true;
};
@@ -74,9 +73,9 @@ pub fn never_extend_bg(
false
}
fn style_at(cell: Cell, styles: &[Style], x: usize) -> Style {
fn style_at(cell: Cell, style_of: &impl Fn(usize) -> Style, x: usize) -> Style {
if cell.has_styling() {
styles.get(x).copied().unwrap_or_default()
style_of(x)
} else {
Style::default()
}
@@ -96,7 +95,7 @@ mod tests {
assert!(never_extend_bg(
Row::default(),
&cells,
&[],
|_| Style::default(),
&color::DEFAULT,
BG
));
@@ -106,7 +105,13 @@ mod tests {
fn prompt_rows_never_extend() {
let mut row = Row::default();
row.set_semantic_prompt(SemanticPrompt::Prompt);
assert!(never_extend_bg(row, &[], &[], &color::DEFAULT, BG));
assert!(never_extend_bg(
row,
&[],
|_| Style::default(),
&color::DEFAULT,
BG
));
}
#[test]
@@ -115,7 +120,7 @@ mod tests {
assert!(never_extend_bg(
Row::default(),
&cells,
&[],
|_| Style::default(),
&color::DEFAULT,
BG
));
@@ -129,7 +134,7 @@ mod tests {
assert!(!never_extend_bg(
Row::default(),
&[c],
&[],
|_| Style::default(),
&color::DEFAULT,
BG
));
@@ -138,7 +143,7 @@ mod tests {
assert!(never_extend_bg(
Row::default(),
&[c],
&[],
|_| Style::default(),
&color::DEFAULT,
BG
));