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
c6a0c49567
commit
062c643ea8
18 files changed
+2195
-82
No files matched your search
@@ -0,0 +1,96 @@
|
||||
//! A single benchmark case (port of `benchmark/Benchmark.zig`).
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// The only error a benchmark can report: it failed and the run ends.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BenchmarkFailed;
|
||||
|
||||
impl std::fmt::Display for BenchmarkFailed {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("benchmark failed")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for BenchmarkFailed {}
|
||||
|
||||
pub type Result<T = ()> = std::result::Result<T, BenchmarkFailed>;
|
||||
|
||||
/// How to run: once, or repeatedly for a duration.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum RunMode {
|
||||
Once,
|
||||
Duration(Duration),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct RunResult {
|
||||
pub iterations: u32,
|
||||
pub duration: Duration,
|
||||
}
|
||||
|
||||
/// The benchmark interface (Zig `Benchmark.VTable`).
|
||||
pub trait Benchmark {
|
||||
/// A single step; the work under test.
|
||||
fn step(&mut self) -> Result;
|
||||
/// Called once before the first step; not measured.
|
||||
fn setup(&mut self) -> Result {
|
||||
Ok(())
|
||||
}
|
||||
/// Called once after the last step.
|
||||
fn teardown(&mut self) {}
|
||||
}
|
||||
|
||||
/// Run the benchmark.
|
||||
pub fn run(b: &mut dyn Benchmark, mode: RunMode) -> Result<RunResult> {
|
||||
b.setup()?;
|
||||
let r = run_steps(b, mode);
|
||||
b.teardown();
|
||||
r
|
||||
}
|
||||
|
||||
fn run_steps(b: &mut dyn Benchmark, mode: RunMode) -> Result<RunResult> {
|
||||
let mut result = RunResult::default();
|
||||
let start = Instant::now();
|
||||
loop {
|
||||
b.step()?;
|
||||
result.iterations += 1;
|
||||
let elapsed = start.elapsed();
|
||||
let exit = match mode {
|
||||
RunMode::Once => true,
|
||||
RunMode::Duration(d) => elapsed >= d,
|
||||
};
|
||||
if exit {
|
||||
result.duration = elapsed;
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Simple {
|
||||
setup_i: usize,
|
||||
step_i: usize,
|
||||
}
|
||||
impl Benchmark for Simple {
|
||||
fn step(&mut self) -> Result {
|
||||
self.step_i += 1;
|
||||
Ok(())
|
||||
}
|
||||
fn setup(&mut self) -> Result {
|
||||
self.setup_i += 1;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn benchmark_once() {
|
||||
let mut s = Simple::default();
|
||||
let r = run(&mut s, RunMode::Once).unwrap();
|
||||
assert_eq!(s.setup_i, 1);
|
||||
assert_eq!(s.step_i, 1);
|
||||
assert_eq!(r.iterations, 1);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user