perf: add ghostty-bench and bring terminal throughput to Zig parity
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
This commit is contained in:
1 parent
8048ee6a05
commit
0c4f0ee412
18 files changed
+2195
-82
No files matched your search
@@ -0,0 +1,64 @@
|
||||
//! `ghostty-synthetic +<ascii|utf8|osc> [--option=value ...]`: endless
|
||||
//! synthetic data on stdout (port of `synthetic/main.zig`).
|
||||
use ghostty_bench::{
|
||||
args::Args,
|
||||
synthetic::{OscStyle, Rng, run_ascii, run_osc, run_sgr, run_utf8},
|
||||
};
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let argv: Vec<String> = std::env::args().skip(1).collect();
|
||||
let mut action = None;
|
||||
for a in &argv {
|
||||
if let Some(name) = a.strip_prefix('+') {
|
||||
if action.is_some() {
|
||||
return Err("multiple actions specified".into());
|
||||
}
|
||||
action = Some(name.to_owned());
|
||||
}
|
||||
}
|
||||
let action = action.ok_or("no action specified; available: ascii, osc, sgr, utf8")?;
|
||||
let mut args = Args::parse(argv)?;
|
||||
let mut rng = Rng::from_time();
|
||||
let mut out = BufWriter::with_capacity(2048, std::io::stdout().lock());
|
||||
let res = match action.as_str() {
|
||||
"ascii" => {
|
||||
args.finish()?;
|
||||
run_ascii(&mut out, &mut rng)
|
||||
}
|
||||
"utf8" => {
|
||||
args.finish()?;
|
||||
run_utf8(&mut out, &mut rng)
|
||||
}
|
||||
"sgr" => {
|
||||
args.finish()?;
|
||||
run_sgr(&mut out, &mut rng)
|
||||
}
|
||||
"osc" => {
|
||||
let p_valid = args.take("p-valid", 0.5f64)?;
|
||||
let style = match args.take_opt_string("style")?.as_deref() {
|
||||
None | Some("streaming") => OscStyle::Streaming,
|
||||
Some("parser") => OscStyle::Parser,
|
||||
Some(s) => return Err(format!("invalid value for --style: {s}")),
|
||||
};
|
||||
args.finish()?;
|
||||
run_osc(&mut out, &mut rng, p_valid, style)
|
||||
}
|
||||
a => return Err(format!("invalid action: +{a}")),
|
||||
};
|
||||
res.map_err(|e| e.to_string())?;
|
||||
// Broken pipe on flush is a normal end.
|
||||
let _ = out.flush();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
eprintln!("error: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user