benchmark: terminal-stream uses the full terminal handler

The terminal-stream benchmark previously used a simplified handler
that handles print actions and drops everything else. That was
originally intended to isolate parse and print throughput, but it
understates the cost of escape-heavy streams: no terminal state is
updated for CSI/OSC/ESC sequences, and because actions are
dispatched at comptime, the unhandled action arms are eliminated
entirely, so the benchmark measures dispatch code that doesn't
exist in the real app.

This switches the benchmark to the full readonly terminal stream
handler (terminal.TerminalStream). Every escape sequence now
updates real terminal state (styles, cursor movement, erases,
modes, etc.), closely mirroring the work the real IO thread does
per byte. This is the handler used to measure the VT throughput
changes in the following commits.

Parser-in-isolation measurement remains covered by the separate
terminal-parser and osc-parser benchmarks, and print throughput is
identical under both handlers since printing flows into the same
Terminal call either way.
This commit is contained in:
Mitchell Hashimoto
2026-07-06 06:10:15 -07:00
parent 0535770e35
commit 258de36d15

View File

@@ -2,15 +2,13 @@
//! handler from input to terminal state update. This is useful to
//! test general throughput of VT parsing and handling.
//!
//! Note that the handler used for this benchmark isn't the full
//! terminal handler, since that requires a significant amount of
//! state. This is a simplified version that only handles specific
//! terminal operations like printing characters. We should expand
//! this to include more operations to improve the accuracy of the
//! benchmark.
//! This uses the full readonly terminal stream handler
//! (terminal.TerminalStream) so every escape sequence updates real
//! terminal state (styles, cursor movement, erases, modes, etc.).
//! This closely mirrors the work done by the real IO thread.
//!
//! It is a fairly broad benchmark that can be used to determine
//! if we need to optimize something more specific (e.g. the parser).
//! For more isolated measurements see the terminal-parser and
//! osc-parser benchmarks.
const TerminalStream = @This();
const std = @import("std");
@@ -20,13 +18,12 @@ const terminalpkg = @import("../terminal/main.zig");
const Benchmark = @import("Benchmark.zig");
const options = @import("options.zig");
const Terminal = terminalpkg.Terminal;
const Stream = terminalpkg.Stream(*Handler);
const Stream = terminalpkg.TerminalStream;
const log = std.log.scoped(.@"terminal-stream-bench");
opts: Options,
terminal: Terminal,
handler: Handler,
stream: Stream,
/// The file, opened in the setup function.
@@ -61,14 +58,15 @@ pub fn create(
.rows = opts.@"terminal-rows",
.cols = opts.@"terminal-cols",
}),
.handler = .{ .t = &ptr.terminal },
.stream = .init(&ptr.handler),
.stream = undefined,
};
ptr.stream = .initAlloc(alloc, .init(&ptr.terminal));
return ptr;
}
pub fn destroy(self: *TerminalStream, alloc: Allocator) void {
self.stream.deinit();
self.terminal.deinit(alloc);
alloc.destroy(self);
}
@@ -129,26 +127,6 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
}
}
/// Implements the handler interface for the terminal.Stream.
/// We should expand this to include more operations to make
/// our benchmark more realistic.
const Handler = struct {
t: *Terminal,
pub fn vt(
self: *Handler,
comptime action: Stream.Action.Tag,
value: Stream.Action.Value(action),
) void {
switch (action) {
.print => self.t.print(value.cp) catch |err| {
log.warn("error processing benchmark print err={}", .{err});
},
else => {},
}
}
};
test TerminalStream {
const testing = std.testing;
const alloc = testing.allocator;