Files
What_That_Claude_Do/winiterm/src/bin/bench_process.rs
T

220 lines
8.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! VT parser throughput benchmark.
//!
//! Measures how quickly the terminal can process raw bytes from a PTY stream.
//! Methodology is modelled on Kitty's `kitten __benchmark__` (without `--render`):
//! 1. Generate representative synthetic data for each scenario.
//! 2. Do one warmup pass.
//! 3. Run `REPETITIONS` timed passes, report MB/s and elapsed time.
//!
//! Run with:
//! cargo run --bin bench_process --release
use std::time::Instant;
use winiterm::terminal::Terminal;
use winiterm::vt_parser::Parser;
const REPETITIONS: usize = 100;
// ─── Data generators ─────────────────────────────────────────────────────────
const ASCII_PRINTABLE: &[u8] =
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ `~!@#$%^&*()_+-=[]{}\\|;:'\",<.>/?";
const CONTROL_CHARS: &[u8] = b"\n\t";
/// Simple linear-congruential PRNG (deterministic, no dependency).
#[inline]
fn lcg(rng: &mut u64) -> u64 {
*rng = rng
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
*rng
}
/// Pick a random byte from `alphabet`.
fn random_byte(rng: &mut u64, alphabet: &[u8]) -> u8 {
alphabet[(lcg(rng) >> 33) as usize % alphabet.len()]
}
/// Generate `len` random bytes from the combined ASCII + control alphabet.
fn random_text(len: usize, rng: &mut u64) -> Vec<u8> {
let alphabet: Vec<u8> = ASCII_PRINTABLE
.iter()
.chain(CONTROL_CHARS.iter())
.copied()
.collect();
(0..len).map(|_| random_byte(rng, &alphabet)).collect()
}
// ─── Benchmark runner ─────────────────────────────────────────────────────────
fn run_benchmark<F>(name: &str, data: &[u8], repetitions: usize, setup: F)
where
F: Fn() -> (Terminal, Parser),
{
// Warmup
{
let (mut term, mut parser) = setup();
parser.parse(data, &mut term);
}
// Timed passes
let start = Instant::now();
for _ in 0..repetitions {
let (mut term, mut parser) = setup();
parser.parse(data, &mut term);
}
let elapsed = start.elapsed();
let total_bytes = data.len() * repetitions;
let mb = total_bytes as f64 / 1_048_576.0;
let rate = mb / elapsed.as_secs_f64();
println!(
" {:<30} : {:>6.2}s {:>8.1} MB/s ({} reps × {:.2} MB)",
name,
elapsed.as_secs_f64(),
rate,
repetitions,
data.len() as f64 / 1_048_576.0,
);
}
fn make_terminal() -> (Terminal, Parser) {
(Terminal::new(80, 25, 20_000), Parser::new())
}
// ─── main ─────────────────────────────────────────────────────────────────────
fn main() {
println!("=== winiterm VT Parser Benchmark ===");
println!("(methodology matches Kitty's kitten __benchmark__ without --render)\n");
// ── 1. Plain ASCII text ───────────────────────────────────────────────────
println!("--- Plain ASCII text ---");
{
let target = 1024 * 2048 + 13;
let mut rng: u64 = 0xDEAD_BEEF;
let data = random_text(target, &mut rng);
run_benchmark("ascii_only", &data, REPETITIONS, make_terminal);
}
// ── 2. CSI escape codes interleaved with text ─────────────────────────────
println!("\n--- CSI codes with text ---");
{
let target = 1024 * 1024 + 17;
let mut rng: u64 = 0x1234_5678;
let mut data: Vec<u8> = Vec::with_capacity(target + 128);
while data.len() < target {
match (lcg(&mut rng) >> 33) % 10 {
0 => {
// Plain text burst (172 chars)
let len = ((lcg(&mut rng) >> 33) % 72 + 1) as usize;
data.extend(random_text(len, &mut rng));
}
1 | 2 => {
// Cursor movement + reset
data.extend_from_slice(b"\x1b[m\x1b[?1h\x1b[H");
}
3 => {
// SGR: bold + italic + colours
data.extend_from_slice(b"\x1b[1;3;31;42m");
}
4 => {
// SGR: 256-colour foreground + background
data.extend_from_slice(b"\x1b[38;5;214;48;5;236m");
}
5 => {
// SGR: true-colour (RGB) foreground
data.extend_from_slice(b"\x1b[38;2;255;128;0m");
}
6 | 7 => {
// Cursor movement + erase
data.extend_from_slice(b"\x1b[m\x1b[5A\x1b[2K\x1b[1J");
}
_ => {
// SGR reset + misc cursor
data.extend_from_slice(b"\x1b[0m\x1b[10;20H\x1b[?25h");
}
}
}
data.extend_from_slice(b"\x1b[m");
run_benchmark("csi_with_text", &data, REPETITIONS, make_terminal);
}
// ── 3. Long OSC sequences (window titles) ────────────────────────────────
println!("\n--- Long OSC sequences ---");
{
let title_body: String = (0..512)
.map(|i| ASCII_PRINTABLE[i % ASCII_PRINTABLE.len()] as char)
.collect();
let mut data: Vec<u8> = Vec::new();
for _ in 0..2048 {
data.extend_from_slice(b"\x1b]2;");
data.extend_from_slice(title_body.as_bytes());
data.push(0x07); // BEL terminator
}
run_benchmark("osc_long_titles", &data, REPETITIONS, make_terminal);
}
// ── 4. Dense colour changes (typical syntax-highlighted code output) ──────
println!("\n--- Dense colour output (syntax highlighting) ---");
{
let mut data: Vec<u8> = Vec::new();
let words = ["fn", "main", "let", "mut", "if", "return", "use", "pub"];
let mut rng: u64 = 0xABCD_EF01;
for _ in 0..8192 {
// Random foreground colour (3045)
let fg = 30 + (lcg(&mut rng) >> 33) % 16;
let word = words[(lcg(&mut rng) >> 33) as usize % words.len()];
let seq = format!("\x1b[{fg}m{word} \x1b[m");
data.extend_from_slice(seq.as_bytes());
}
run_benchmark("dense_color_output", &data, REPETITIONS, make_terminal);
}
// ── 5. Mixed: scroll + insert/delete lines ────────────────────────────────
println!("\n--- Scroll-heavy output ---");
{
let mut data: Vec<u8> = Vec::new();
let mut rng: u64 = 0xFEED_FACE;
for _ in 0..4096 {
data.extend(random_text(60, &mut rng));
data.push(b'\n');
// Occasionally insert/delete lines
if (lcg(&mut rng) >> 33) % 8 == 0 {
data.extend_from_slice(b"\x1b[1L"); // insert line
}
}
run_benchmark("scroll_heavy", &data, REPETITIONS, make_terminal);
}
println!("\n=== Benchmark complete ===");
println!("Note: measures CPU parse + terminal state updates only (no GPU rendering).");
println!("Compare against Kitty: kitten __benchmark__ (without --render flag)");
}
// ─── Minimal `write!` support for building data vecs ─────────────────────────
use std::fmt::Write as FmtWrite;
struct VecWriter<'a>(&'a mut Vec<u8>);
impl FmtWrite for VecWriter<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.0.extend_from_slice(s.as_bytes());
Ok(())
}
}
// Override the `write!` macro to work with our VecWriter.
trait WriteVec {
fn write_fmt(&mut self, args: std::fmt::Arguments<'_>);
}
impl WriteVec for Vec<u8> {
fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) {
let _ = std::fmt::write(&mut VecWriter(self), args);
}
}