From 30a14d230ed118363650259aca953474cd3681ed Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 1 Sep 2022 17:53:40 -0700 Subject: [PATCH] process ASCII events manually to avoid function call overhead --- src/Window.zig | 40 ++++++++++++++++++++++++++++++++++++--- src/terminal/Parser.zig | 4 ++++ src/terminal/Screen.zig | 4 ++++ src/terminal/Terminal.zig | 8 +++++++- src/terminal/main.zig | 1 + 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index f0407f7e8..6b1674dcf 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -1271,9 +1271,43 @@ fn ttyRead(t: *libuv.Tty, n: isize, buf: []const u8) void { // Schedule a render win.render_timer.schedule() catch unreachable; - // Process the terminal data - win.terminal_stream.nextSlice(buf[0..@intCast(usize, n)]) catch |err| - log.err("error processing terminal data: {}", .{err}); + // Process the terminal data. This is an extremely hot part of the + // terminal emulator, so we do some abstraction leakage to avoid + // function calls and unnecessary logic. + // + // The ground state is the only state that we can see and print/execute + // ASCII, so we only execute this hot path if we're already in the ground + // state. + // + // Empirically, this alone improved throughput of large text output by ~20%. + var i: usize = 0; + const end = @intCast(usize, n); + if (win.terminal_stream.parser.state == .ground) { + for (buf[i..end]) |c| { + switch (terminal.parse_table.table[c][@enumToInt(terminal.Parser.State.ground)].action) { + // Print, call directly. + .print => win.print(@intCast(u21, c)) catch |err| + log.err("error processing terminal data: {}", .{err}), + + // C0 execute, let our stream handle this one but otherwise + // continue since we're guaranteed to be back in ground. + .execute => win.terminal_stream.next(c) catch |err| + log.err("error processing terminal data: {}", .{err}), + + // Otherwise, break out and go the slow path until we're + // back in ground. + else => break, + } + + i += 1; + } + } + + if (i < end) { + //log.warn("SLOW={}", .{end - i}); + win.terminal_stream.nextSlice(buf[i..end]) catch |err| + log.err("error processing terminal data: {}", .{err}); + } } fn ttyWrite(req: *libuv.WriteReq, status: i32) void { diff --git a/src/terminal/Parser.zig b/src/terminal/Parser.zig index cd77007e9..032fa42d9 100644 --- a/src/terminal/Parser.zig +++ b/src/terminal/Parser.zig @@ -6,6 +6,7 @@ const Parser = @This(); const std = @import("std"); const builtin = @import("builtin"); +const trace = @import("tracy").trace; const testing = std.testing; const table = @import("parse_table.zig").table; const osc = @import("osc.zig"); @@ -212,6 +213,9 @@ pub fn init() Parser { /// Up to 3 actions may need to be exected -- in order -- representing /// the state exit, transition, and entry actions. pub fn next(self: *Parser, c: u8) [3]?Action { + const tracy = trace(@src()); + defer tracy.end(); + // If we're processing UTF-8, we handle this manually. if (self.state == .utf8) { return .{ self.next_utf8(c), null, null }; diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 38b704831..169d9171c 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -22,6 +22,7 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const utf8proc = @import("utf8proc"); +const trace = @import("tracy").trace; const color = @import("color.zig"); const point = @import("point.zig"); const CircBuf = @import("circ_buf.zig").CircBuf; @@ -432,6 +433,9 @@ pub fn rowIterator(self: *Screen, tag: RowIndexTag) RowIterator { /// Returns the row at the given index. This row is writable, although /// only the active area should probably be written to. pub fn getRow(self: *Screen, index: RowIndex) Row { + const tracy = trace(@src()); + defer tracy.end(); + // Get our offset into storage const offset = index.toScreen(self).screen * (self.cols + 1); diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 8b15ef055..7650d032a 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -464,7 +464,7 @@ pub fn print(self: *Terminal, c: u21) !void { switch (width) { // Single cell is very easy: just write in the cell - 1 => _ = self.printCell(c), + 1 => _ = @call(.{ .modifier = .always_inline }, self.printCell, .{c}), // Wide character requires a spacer. We print this by // using two cells: the first is flagged "wide" and has the @@ -505,6 +505,9 @@ pub fn print(self: *Terminal, c: u21) !void { } fn printCell(self: *Terminal, unmapped_c: u21) *Screen.Cell { + // const tracy = trace(@src()); + // defer tracy.end(); + const c = c: { // TODO: non-utf8 handling, gr @@ -558,6 +561,9 @@ fn printCell(self: *Terminal, unmapped_c: u21) *Screen.Cell { } fn printWrap(self: *Terminal) !void { + const tracy = trace(@src()); + defer tracy.end(); + const row = self.screen.getRow(.{ .active = self.screen.cursor.y }); row.setWrapped(true); diff --git a/src/terminal/main.zig b/src/terminal/main.zig index 097bade52..4472116c6 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -7,6 +7,7 @@ const csi = @import("csi.zig"); const sgr = @import("sgr.zig"); pub const point = @import("point.zig"); pub const color = @import("color.zig"); +pub const parse_table = @import("parse_table.zig"); pub const Charset = charsets.Charset; pub const CharsetSlot = charsets.Slots;