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:
1 parent
d89691b0ce
commit
abb89a80e9
7 files changed
+177
-56
No files matched your search
@@ -154,6 +154,12 @@ impl Cell {
|
||||
pub fn has_styling(self) -> bool {
|
||||
self.style_id() != 0
|
||||
}
|
||||
/// True if the cell is a plain single-codepoint cell with the default
|
||||
/// style (content tag `Codepoint` and style id 0): a single mask test.
|
||||
#[inline(always)]
|
||||
pub fn is_plain(self) -> bool {
|
||||
self.0 & (3 | (0xFFFF << 26)) == 0
|
||||
}
|
||||
/// True for a blank narrow textual cell; style does not affect this test.
|
||||
pub fn is_empty(self) -> bool {
|
||||
(self.content_tag() as u8) < 2 && !self.has_text() && self.wide() == Wide::Narrow
|
||||
@@ -1789,6 +1795,19 @@ fn swap_map<T, S: std::hash::BuildHasher>(map: &mut HashMap<usize, T, S>, a: usi
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn cell_is_plain() {
|
||||
let mut c = Cell::init(u32::from('a'));
|
||||
assert!(c.is_plain());
|
||||
c.set_style_id(1);
|
||||
assert!(!c.is_plain());
|
||||
c.set_style_id(0);
|
||||
c.set_content_tag(ContentTag::BgColorRgb);
|
||||
assert!(!c.is_plain());
|
||||
c.set_content_tag(ContentTag::CodepointGrapheme);
|
||||
assert!(!c.is_plain());
|
||||
}
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn chunk_alloc_matches_naive_first_fit() {
|
||||
|
||||
@@ -205,15 +205,18 @@ pub struct Cell<'a> {
|
||||
/// `MultiArrayList(Cell)`) so that the common plain-text case only copies the
|
||||
/// raw cells.
|
||||
///
|
||||
/// `raw` always has exactly `cols` entries. `grapheme` and `style` either
|
||||
/// `raw` always has exactly `cols` entries. `grapheme` and `style_ids` either
|
||||
/// have exactly `cols` entries, or are empty when the source row has no
|
||||
/// managed memory (in which case every cell has no grapheme and the default
|
||||
/// style). Always use the accessors rather than indexing those two directly.
|
||||
/// style). Styles are stored sparsely: `style_ids[x]` is 0 for the default
|
||||
/// style, otherwise `styles[style_ids[x] - 1]`. Always use the accessors
|
||||
/// rather than indexing those directly.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Cells {
|
||||
pub raw: Vec<page::Cell>,
|
||||
pub grapheme: Vec<Vec<u32>>,
|
||||
pub style: Vec<Style>,
|
||||
pub style_ids: Vec<u16>,
|
||||
pub styles: Vec<Style>,
|
||||
}
|
||||
|
||||
impl Cells {
|
||||
@@ -232,7 +235,10 @@ impl Cells {
|
||||
|
||||
/// The style of the cell at `x` (default style if none).
|
||||
pub fn style(&self, x: usize) -> Style {
|
||||
self.style.get(x).copied().unwrap_or_default()
|
||||
match self.style_ids.get(x) {
|
||||
Some(&id) if id > 0 => self.styles[usize::from(id) - 1],
|
||||
_ => Style::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The full cell at `x`. Panics if out of range.
|
||||
@@ -252,7 +258,8 @@ impl Cells {
|
||||
fn clear(&mut self) {
|
||||
self.raw.clear();
|
||||
self.grapheme.clear();
|
||||
self.style.clear();
|
||||
self.style_ids.clear();
|
||||
self.styles.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,12 +558,28 @@ impl RenderState {
|
||||
continue;
|
||||
}
|
||||
|
||||
row.cells.style.resize(cols, Style::default());
|
||||
row.cells.style_ids.resize(cols, 0);
|
||||
// Cache of the last page style id and its index in `styles`.
|
||||
let mut last_sid: u16 = 0;
|
||||
let mut last_idx: u16 = 0;
|
||||
let mut last_bg_key: u32 = 0;
|
||||
let mut last_bg_idx: u16 = 0;
|
||||
for (x, page_cell) in page_cells.iter().enumerate() {
|
||||
// Plain cells (the vast majority) need nothing more.
|
||||
if page_cell.is_plain() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Append assuming its a single-codepoint, styled cell
|
||||
// (most common by far).
|
||||
if page_cell.style_id() > 0 {
|
||||
row.cells.style[x] = p.style(*page_cell);
|
||||
let sid = page_cell.style_id();
|
||||
if sid > 0 {
|
||||
if sid != last_sid {
|
||||
row.cells.styles.push(p.style(*page_cell));
|
||||
last_sid = sid;
|
||||
last_idx = row.cells.styles.len() as u16;
|
||||
}
|
||||
row.cells.style_ids[x] = last_idx;
|
||||
}
|
||||
|
||||
// Switch on our content tag to handle less likely cases.
|
||||
@@ -580,17 +603,32 @@ impl RenderState {
|
||||
}
|
||||
|
||||
ContentTag::BgColorRgb => {
|
||||
row.cells.style[x] = Style {
|
||||
bg_color: Color::Rgb(page_cell.color_rgb()),
|
||||
..Style::default()
|
||||
};
|
||||
let c = page_cell.color_rgb();
|
||||
let key =
|
||||
1 << 24 | u32::from(c.r) << 16 | u32::from(c.g) << 8 | u32::from(c.b);
|
||||
if key != last_bg_key {
|
||||
row.cells.styles.push(Style {
|
||||
bg_color: Color::Rgb(c),
|
||||
..Style::default()
|
||||
});
|
||||
last_bg_key = key;
|
||||
last_bg_idx = row.cells.styles.len() as u16;
|
||||
}
|
||||
row.cells.style_ids[x] = last_bg_idx;
|
||||
}
|
||||
|
||||
ContentTag::BgColorPalette => {
|
||||
row.cells.style[x] = Style {
|
||||
bg_color: Color::Palette(page_cell.color_palette()),
|
||||
..Style::default()
|
||||
};
|
||||
let c = page_cell.color_palette();
|
||||
let key = 2 << 24 | u32::from(c);
|
||||
if key != last_bg_key {
|
||||
row.cells.styles.push(Style {
|
||||
bg_color: Color::Palette(c),
|
||||
..Style::default()
|
||||
});
|
||||
last_bg_key = key;
|
||||
last_bg_idx = row.cells.styles.len() as u16;
|
||||
}
|
||||
row.cells.style_ids[x] = last_bg_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user