terminal: fix up some performance issues with render state

pull/9662/head
Mitchell Hashimoto 2025-11-18 13:11:19 -10:00
parent 5d85f2382e
commit 4caefb807c
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
3 changed files with 54 additions and 54 deletions

View File

@ -91,6 +91,13 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
// Always reset our terminal state
self.terminal.fullReset();
// Force a style on every single row, which
var s = self.terminal.vtStream();
defer s.deinit();
s.nextSlice("\x1b[48;2;20;40;60m") catch unreachable;
for (0..self.terminal.rows - 1) |_| s.nextSlice("hello\r\n") catch unreachable;
s.nextSlice("hello") catch unreachable;
// Setup our terminal state
const data_f: std.fs.File = (options.dataFile(
self.opts.data,

View File

@ -1800,8 +1800,9 @@ pub const Row = packed struct(u64) {
/// Returns true if this row has any managed memory outside of the
/// row structure (graphemes, styles, etc.)
inline fn managedMemory(self: Row) bool {
return self.grapheme or self.styled or self.hyperlink;
pub inline fn managedMemory(self: Row) bool {
// Ordered on purpose for likelyhood.
return self.styled or self.hyperlink or self.grapheme;
}
};

View File

@ -2,6 +2,7 @@ const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const fastmem = @import("../fastmem.zig");
const size = @import("size.zig");
const page = @import("page.zig");
const Screen = @import("Screen.zig");
@ -89,15 +90,19 @@ pub const RenderState = struct {
};
pub const Cell = struct {
content: Content,
wide: page.Cell.Wide,
style: Style,
/// Always set, this is the raw copied cell data from page.Cell.
/// The managed memory (hyperlinks, graphames, etc.) is NOT safe
/// to access from here. It is duplicated into the other fields if
/// it exists.
raw: page.Cell,
pub const Content = union(enum) {
empty,
single: u21,
slice: []const u21,
};
/// Grapheme data for the cell. This is undefined unless the
/// raw cell's content_tag is `codepoint_grapheme`.
grapheme: []const u21,
/// The style data for the cell. This is undefined unless
/// the style_id is non-default on raw.
style: Style,
};
pub fn deinit(self: *RenderState, alloc: Allocator) void {
@ -216,7 +221,6 @@ pub const RenderState = struct {
// restore it on all exit paths so we don't leak memory.
var arena = row_arenas[y].promote(alloc);
defer row_arenas[y] = arena.state;
const arena_alloc = arena.allocator();
// Reset our cells if we're rebuilding this row.
if (row_cells[y].len > 0) {
@ -239,63 +243,53 @@ pub const RenderState = struct {
// Our per-row arena is only used for temporary allocations
// pertaining to cells directly (e.g. graphemes, hyperlinks).
const cells: *std.MultiArrayList(Cell) = &row_cells[y];
try cells.ensureTotalCapacity(alloc, self.cols);
try cells.resize(alloc, self.cols);
for (page_cells) |*page_cell| {
// We always copy our raw cell data. In the case we have no
// managed memory, we can skip setting any other fields.
//
// This is an important optimization. For plain-text screens
// this ends up being something around 300% faster based on
// the `screen-clone` benchmark.
const cells_slice = cells.slice();
fastmem.copy(
page.Cell,
cells_slice.items(.raw),
page_cells,
);
if (!page_rac.row.managedMemory()) continue;
const arena_alloc = arena.allocator();
const cells_grapheme = cells_slice.items(.grapheme);
const cells_style = cells_slice.items(.style);
for (page_cells, 0..) |*page_cell, x| {
// Append assuming its a single-codepoint, styled cell
// (most common by far).
const idx = cells.len;
cells.appendAssumeCapacity(.{
.content = .empty, // Filled in below
.wide = page_cell.wide,
.style = if (page_cell.style_id > 0) p.styles.get(
if (page_cell.style_id > 0) cells_style[x] = p.styles.get(
p.memory,
page_cell.style_id,
).* else .{},
});
).*;
// Switch on our content tag to handle less likely cases.
switch (page_cell.content_tag) {
.codepoint => {
@branchHint(.likely);
// It is possible for our codepoint to be zero. If
// that is the case, we set the codepoint to empty.
const cp = page_cell.content.codepoint;
var content = cells.items(.content);
content[idx] = if (cp == 0) zero: {
// Spacers are meaningful and not actually empty
// so we only set empty for truly empty cells.
if (page_cell.wide == .narrow) {
@branchHint(.likely);
break :zero .empty;
}
break :zero .{ .single = ' ' };
} else .{
.single = cp,
};
// Primary codepoint goes into `raw` field.
},
// If we have a multi-codepoint grapheme, look it up and
// set our content type.
.codepoint_grapheme => grapheme: {
.codepoint_grapheme => {
@branchHint(.unlikely);
const extra = p.lookupGrapheme(page_cell) orelse break :grapheme;
var cps = try arena_alloc.alloc(u21, extra.len + 1);
cps[0] = page_cell.content.codepoint;
@memcpy(cps[1..], extra);
var content = cells.items(.content);
content[idx] = .{ .slice = cps };
cells_grapheme[x] = try arena_alloc.dupe(
u21,
p.lookupGrapheme(page_cell) orelse &.{},
);
},
.bg_color_rgb => {
@branchHint(.unlikely);
var content = cells.items(.style);
content[idx] = .{ .bg_color = .{ .rgb = .{
cells_style[x] = .{ .bg_color = .{ .rgb = .{
.r = page_cell.content.color_rgb.r,
.g = page_cell.content.color_rgb.g,
.b = page_cell.content.color_rgb.b,
@ -304,9 +298,7 @@ pub const RenderState = struct {
.bg_color_palette => {
@branchHint(.unlikely);
var content = cells.items(.style);
content[idx] = .{ .bg_color = .{
cells_style[x] = .{ .bg_color = .{
.palette = page_cell.content.color_palette,
} };
},