windows terminal originally basing the idea from zterm
This commit is contained in:
@@ -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 (1–72 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 (30–45)
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user