mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-18 09:46:07 +03:00
remove src/bench
This commit is contained in:
@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# This is a trivial helper script to help run the stream benchmark.
|
|
||||||
# You probably want to tweak this script depending on what you're
|
|
||||||
# trying to measure.
|
|
||||||
|
|
||||||
# Options:
|
|
||||||
# - "ascii", uniform random ASCII bytes
|
|
||||||
# - "utf8", uniform random unicode characters, encoded as utf8
|
|
||||||
# - "rand", pure random data, will contain many invalid code sequences.
|
|
||||||
DATA="ascii"
|
|
||||||
SIZE="25000000"
|
|
||||||
|
|
||||||
# Uncomment to test with an active terminal state.
|
|
||||||
# ARGS=" --terminal"
|
|
||||||
|
|
||||||
# Generate the benchmark input ahead of time so it's not included in the time.
|
|
||||||
./zig-out/bin/bench-stream --mode=gen-$DATA | head -c $SIZE > /tmp/ghostty_bench_data
|
|
||||||
|
|
||||||
# Uncomment to instead use the contents of `stream.txt` as input. (Ignores SIZE)
|
|
||||||
# echo $(cat ./stream.txt) > /tmp/ghostty_bench_data
|
|
||||||
|
|
||||||
hyperfine \
|
|
||||||
--warmup 10 \
|
|
||||||
-n memcpy \
|
|
||||||
"./zig-out/bin/bench-stream --mode=noop${ARGS} </tmp/ghostty_bench_data" \
|
|
||||||
-n scalar \
|
|
||||||
"./zig-out/bin/bench-stream --mode=scalar${ARGS} </tmp/ghostty_bench_data" \
|
|
||||||
-n simd \
|
|
||||||
"./zig-out/bin/bench-stream --mode=simd${ARGS} </tmp/ghostty_bench_data"
|
|
@ -1,253 +0,0 @@
|
|||||||
//! This benchmark tests the throughput of the VT stream. It has a few
|
|
||||||
//! modes in order to test different methods of stream processing. It
|
|
||||||
//! provides a "noop" mode to give us the `memcpy` speed.
|
|
||||||
//!
|
|
||||||
//! This will consume all of the available stdin, so you should run it
|
|
||||||
//! with `head` in a pipe to restrict. For example, to test ASCII input:
|
|
||||||
//!
|
|
||||||
//! bench-stream --mode=gen-ascii | head -c 50M | bench-stream --mode=simd
|
|
||||||
//!
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
|
||||||
const cli = @import("../cli.zig");
|
|
||||||
const terminal = @import("../terminal/main.zig");
|
|
||||||
const synthetic = @import("../synthetic/main.zig");
|
|
||||||
|
|
||||||
const Args = struct {
|
|
||||||
mode: Mode = .noop,
|
|
||||||
|
|
||||||
/// The PRNG seed used by the input generators.
|
|
||||||
/// -1 uses a random seed (default)
|
|
||||||
seed: i64 = -1,
|
|
||||||
|
|
||||||
/// Process input with a real terminal. This will be MUCH slower than
|
|
||||||
/// the other modes because it has to maintain terminal state but will
|
|
||||||
/// help get more realistic numbers.
|
|
||||||
terminal: Terminal = .none,
|
|
||||||
@"terminal-rows": usize = 80,
|
|
||||||
@"terminal-cols": usize = 120,
|
|
||||||
|
|
||||||
/// The size for read buffers. Doesn't usually need to be changed. The
|
|
||||||
/// main point is to make this runtime known so we can avoid compiler
|
|
||||||
/// optimizations.
|
|
||||||
@"buffer-size": usize = 4096,
|
|
||||||
|
|
||||||
/// This is set by the CLI parser for deinit.
|
|
||||||
_arena: ?ArenaAllocator = null,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Args) void {
|
|
||||||
if (self._arena) |arena| arena.deinit();
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Terminal = enum { none, new };
|
|
||||||
};
|
|
||||||
|
|
||||||
const Mode = enum {
|
|
||||||
// Do nothing, just read from stdin into a stack-allocated buffer.
|
|
||||||
// This is used to benchmark our base-case: it gives us our maximum
|
|
||||||
// throughput on a basic read.
|
|
||||||
noop,
|
|
||||||
|
|
||||||
// These benchmark the throughput of the terminal stream parsing
|
|
||||||
// with and without SIMD. The "simd" option will use whatever is best
|
|
||||||
// for the running platform.
|
|
||||||
//
|
|
||||||
// Note that these run through the full VT parser but do not apply
|
|
||||||
// the operations to terminal state, so there is no terminal state
|
|
||||||
// overhead.
|
|
||||||
scalar,
|
|
||||||
simd,
|
|
||||||
|
|
||||||
// Generate an infinite stream of random printable ASCII characters.
|
|
||||||
@"gen-ascii",
|
|
||||||
|
|
||||||
// Generate an infinite stream of random printable unicode characters.
|
|
||||||
@"gen-utf8",
|
|
||||||
|
|
||||||
// Generate an infinite stream of arbitrary random bytes.
|
|
||||||
@"gen-rand",
|
|
||||||
|
|
||||||
// Generate an infinite stream of OSC requests. These will be mixed
|
|
||||||
// with valid and invalid OSC requests by default, but the
|
|
||||||
// `-valid` and `-invalid`-suffixed variants can be used to get only
|
|
||||||
// a specific type of OSC request.
|
|
||||||
@"gen-osc",
|
|
||||||
@"gen-osc-valid",
|
|
||||||
@"gen-osc-invalid",
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const std_options: std.Options = .{
|
|
||||||
.log_level = .debug,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
// We want to use the c allocator because it is much faster than GPA.
|
|
||||||
const alloc = std.heap.c_allocator;
|
|
||||||
|
|
||||||
// Parse our args
|
|
||||||
var args: Args = .{};
|
|
||||||
defer args.deinit();
|
|
||||||
{
|
|
||||||
var iter = try cli.args.argsIterator(alloc);
|
|
||||||
defer iter.deinit();
|
|
||||||
try cli.args.parse(Args, alloc, &args, &iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = std.io.getStdIn().reader();
|
|
||||||
const writer = std.io.getStdOut().writer();
|
|
||||||
const buf = try alloc.alloc(u8, args.@"buffer-size");
|
|
||||||
|
|
||||||
// Build our RNG
|
|
||||||
const seed: u64 = if (args.seed >= 0) @bitCast(args.seed) else @truncate(@as(u128, @bitCast(std.time.nanoTimestamp())));
|
|
||||||
var prng = std.Random.DefaultPrng.init(seed);
|
|
||||||
const rand = prng.random();
|
|
||||||
|
|
||||||
// Handle the modes that do not depend on terminal state first.
|
|
||||||
switch (args.mode) {
|
|
||||||
.@"gen-ascii" => {
|
|
||||||
var gen: synthetic.Bytes = .{
|
|
||||||
.rand = rand,
|
|
||||||
.alphabet = synthetic.Bytes.Alphabet.ascii,
|
|
||||||
};
|
|
||||||
try generate(writer, gen.generator());
|
|
||||||
},
|
|
||||||
|
|
||||||
.@"gen-utf8" => {
|
|
||||||
var gen: synthetic.Utf8 = .{
|
|
||||||
.rand = rand,
|
|
||||||
};
|
|
||||||
try generate(writer, gen.generator());
|
|
||||||
},
|
|
||||||
|
|
||||||
.@"gen-rand" => {
|
|
||||||
var gen: synthetic.Bytes = .{ .rand = rand };
|
|
||||||
try generate(writer, gen.generator());
|
|
||||||
},
|
|
||||||
|
|
||||||
.@"gen-osc" => {
|
|
||||||
var gen: synthetic.Osc = .{
|
|
||||||
.rand = rand,
|
|
||||||
.p_valid = 0.5,
|
|
||||||
};
|
|
||||||
try generate(writer, gen.generator());
|
|
||||||
},
|
|
||||||
|
|
||||||
.@"gen-osc-valid" => {
|
|
||||||
var gen: synthetic.Osc = .{
|
|
||||||
.rand = rand,
|
|
||||||
.p_valid = 1.0,
|
|
||||||
};
|
|
||||||
try generate(writer, gen.generator());
|
|
||||||
},
|
|
||||||
|
|
||||||
.@"gen-osc-invalid" => {
|
|
||||||
var gen: synthetic.Osc = .{
|
|
||||||
.rand = rand,
|
|
||||||
.p_valid = 0.0,
|
|
||||||
};
|
|
||||||
try generate(writer, gen.generator());
|
|
||||||
},
|
|
||||||
|
|
||||||
.noop => try benchNoop(reader, buf),
|
|
||||||
|
|
||||||
// Handle the ones that depend on terminal state next
|
|
||||||
inline .scalar,
|
|
||||||
.simd,
|
|
||||||
=> |tag| switch (args.terminal) {
|
|
||||||
.new => {
|
|
||||||
const TerminalStream = terminal.Stream(*TerminalHandler);
|
|
||||||
var t = try terminal.Terminal.init(alloc, .{
|
|
||||||
.cols = @intCast(args.@"terminal-cols"),
|
|
||||||
.rows = @intCast(args.@"terminal-rows"),
|
|
||||||
});
|
|
||||||
var handler: TerminalHandler = .{ .t = &t };
|
|
||||||
var stream: TerminalStream = .{ .handler = &handler };
|
|
||||||
switch (tag) {
|
|
||||||
.scalar => try benchScalar(reader, &stream, buf),
|
|
||||||
.simd => try benchSimd(reader, &stream, buf),
|
|
||||||
else => @compileError("missing case"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.none => {
|
|
||||||
var stream: terminal.Stream(NoopHandler) = .{ .handler = .{} };
|
|
||||||
switch (tag) {
|
|
||||||
.scalar => try benchScalar(reader, &stream, buf),
|
|
||||||
.simd => try benchSimd(reader, &stream, buf),
|
|
||||||
else => @compileError("missing case"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate(
|
|
||||||
writer: anytype,
|
|
||||||
gen: synthetic.Generator,
|
|
||||||
) !void {
|
|
||||||
var buf: [1024]u8 = undefined;
|
|
||||||
while (true) {
|
|
||||||
const data = try gen.next(&buf);
|
|
||||||
writer.writeAll(data) catch |err| switch (err) {
|
|
||||||
error.BrokenPipe => return, // stdout closed
|
|
||||||
else => return err,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
noinline fn benchNoop(reader: anytype, buf: []u8) !void {
|
|
||||||
var total: usize = 0;
|
|
||||||
while (true) {
|
|
||||||
const n = try reader.readAll(buf);
|
|
||||||
if (n == 0) break;
|
|
||||||
total += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
std.log.info("total bytes len={}", .{total});
|
|
||||||
}
|
|
||||||
|
|
||||||
noinline fn benchScalar(
|
|
||||||
reader: anytype,
|
|
||||||
stream: anytype,
|
|
||||||
buf: []u8,
|
|
||||||
) !void {
|
|
||||||
while (true) {
|
|
||||||
const n = try reader.read(buf);
|
|
||||||
if (n == 0) break;
|
|
||||||
|
|
||||||
// Using stream.next directly with a for loop applies a naive
|
|
||||||
// scalar approach.
|
|
||||||
for (buf[0..n]) |c| try stream.next(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
noinline fn benchSimd(
|
|
||||||
reader: anytype,
|
|
||||||
stream: anytype,
|
|
||||||
buf: []u8,
|
|
||||||
) !void {
|
|
||||||
while (true) {
|
|
||||||
const n = try reader.read(buf);
|
|
||||||
if (n == 0) break;
|
|
||||||
try stream.nextSlice(buf[0..n]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const NoopHandler = struct {
|
|
||||||
pub fn print(self: NoopHandler, cp: u21) !void {
|
|
||||||
_ = self;
|
|
||||||
_ = cp;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const TerminalHandler = struct {
|
|
||||||
t: *terminal.Terminal,
|
|
||||||
|
|
||||||
pub fn print(self: *TerminalHandler, cp: u21) !void {
|
|
||||||
try self.t.print(cp);
|
|
||||||
}
|
|
||||||
};
|
|
@ -528,7 +528,6 @@ pub const ExeEntrypoint = enum {
|
|||||||
webgen_config,
|
webgen_config,
|
||||||
webgen_actions,
|
webgen_actions,
|
||||||
webgen_commands,
|
webgen_commands,
|
||||||
bench_stream,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The release channel for the build.
|
/// The release channel for the build.
|
||||||
|
@ -47,54 +47,6 @@ pub fn init(
|
|||||||
try steps.append(exe);
|
try steps.append(exe);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the directory ./src/bench
|
|
||||||
const c_dir_path = b.pathFromRoot("src/bench");
|
|
||||||
var c_dir = try std.fs.cwd().openDir(c_dir_path, .{ .iterate = true });
|
|
||||||
defer c_dir.close();
|
|
||||||
|
|
||||||
// Go through and add each as a step
|
|
||||||
var c_dir_it = c_dir.iterate();
|
|
||||||
while (try c_dir_it.next()) |entry| {
|
|
||||||
// Get the index of the last '.' so we can strip the extension.
|
|
||||||
const index = std.mem.lastIndexOfScalar(u8, entry.name, '.') orelse continue;
|
|
||||||
if (index == 0) continue;
|
|
||||||
|
|
||||||
// If it doesn't end in 'zig' then ignore
|
|
||||||
if (!std.mem.eql(u8, entry.name[index + 1 ..], "zig")) continue;
|
|
||||||
|
|
||||||
// Name of the conformance app and full path to the entrypoint.
|
|
||||||
const name = entry.name[0..index];
|
|
||||||
|
|
||||||
// Executable builder.
|
|
||||||
const bin_name = try std.fmt.allocPrint(b.allocator, "bench-{s}", .{name});
|
|
||||||
const c_exe = b.addExecutable(.{
|
|
||||||
.name = bin_name,
|
|
||||||
.root_module = b.createModule(.{
|
|
||||||
.root_source_file = b.path("src/main.zig"),
|
|
||||||
.target = deps.config.target,
|
|
||||||
|
|
||||||
// We always want our benchmarks to be in release mode.
|
|
||||||
.optimize = .ReleaseFast,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
c_exe.linkLibC();
|
|
||||||
|
|
||||||
// Update our entrypoint
|
|
||||||
var enum_name: [64]u8 = undefined;
|
|
||||||
@memcpy(enum_name[0..name.len], name);
|
|
||||||
std.mem.replaceScalar(u8, enum_name[0..name.len], '-', '_');
|
|
||||||
|
|
||||||
var buf: [64]u8 = undefined;
|
|
||||||
const new_deps = try deps.changeEntrypoint(b, std.meta.stringToEnum(
|
|
||||||
Config.ExeEntrypoint,
|
|
||||||
try std.fmt.bufPrint(&buf, "bench_{s}", .{enum_name[0..name.len]}),
|
|
||||||
).?);
|
|
||||||
|
|
||||||
_ = try new_deps.add(c_exe);
|
|
||||||
|
|
||||||
try steps.append(c_exe);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{ .steps = steps.items };
|
return .{ .steps = steps.items };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ const entrypoint = switch (build_config.exe_entrypoint) {
|
|||||||
.webgen_config => @import("build/webgen/main_config.zig"),
|
.webgen_config => @import("build/webgen/main_config.zig"),
|
||||||
.webgen_actions => @import("build/webgen/main_actions.zig"),
|
.webgen_actions => @import("build/webgen/main_actions.zig"),
|
||||||
.webgen_commands => @import("build/webgen/main_commands.zig"),
|
.webgen_commands => @import("build/webgen/main_commands.zig"),
|
||||||
.bench_stream => @import("bench/stream.zig"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The main entrypoint for the program.
|
/// The main entrypoint for the program.
|
||||||
|
Reference in New Issue
Block a user