windows terminal originally basing the idea from zterm

This commit is contained in:
2026-06-09 11:02:26 +02:00
parent 775c222448
commit 9e50d14d63
38 changed files with 9631 additions and 878 deletions
+219
View File
@@ -0,0 +1,219 @@
//! 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);
}
}