mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-06-10 05:38:16 +00:00
trying a bunch of things to get performance to match
This commit is contained in:
@@ -10,6 +10,7 @@ const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Benchmark = @import("Benchmark.zig");
|
||||
const options = @import("options.zig");
|
||||
const uucode = @import("uucode");
|
||||
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
|
||||
const simd = @import("../simd/main.zig");
|
||||
const table = @import("../unicode/main.zig").table;
|
||||
@@ -47,6 +48,9 @@ pub const Mode = enum {
|
||||
|
||||
/// Test our lookup table implementation.
|
||||
table,
|
||||
|
||||
/// Using uucode, with custom `width` extension based on `wcwidth`.
|
||||
uucode,
|
||||
};
|
||||
|
||||
/// Create a new terminal stream handler for the given arguments.
|
||||
@@ -71,6 +75,7 @@ pub fn benchmark(self: *CodepointWidth) Benchmark {
|
||||
.wcwidth => stepWcwidth,
|
||||
.table => stepTable,
|
||||
.simd => stepSimd,
|
||||
.uucode => stepUucode,
|
||||
},
|
||||
.setupFn = setup,
|
||||
.teardownFn = teardown,
|
||||
@@ -192,6 +197,41 @@ fn stepSimd(ptr: *anyopaque) Benchmark.Error!void {
|
||||
}
|
||||
}
|
||||
|
||||
fn stepUucode(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
|
||||
|
||||
const f = self.data_f orelse return;
|
||||
var r = std.io.bufferedReader(f.reader());
|
||||
var d: UTF8Decoder = .{};
|
||||
var buf: [4096]u8 = undefined;
|
||||
while (true) {
|
||||
const n = r.read(&buf) catch |err| {
|
||||
log.warn("error reading data file err={}", .{err});
|
||||
return error.BenchmarkFailed;
|
||||
};
|
||||
if (n == 0) break; // EOF reached
|
||||
|
||||
for (buf[0..n]) |c| {
|
||||
const cp_, const consumed = d.next(c);
|
||||
assert(consumed);
|
||||
if (cp_) |cp| {
|
||||
// This is the same trick we do in terminal.zig so we
|
||||
// keep it here.
|
||||
const width = if (cp <= 0xFF)
|
||||
1
|
||||
else
|
||||
//uucode.getX(.width, @intCast(cp));
|
||||
//uucode.getWidth(@intCast(cp));
|
||||
uucode.getSpecial(@intCast(cp)).width;
|
||||
|
||||
// Write the width to the buffer to avoid it being compiled
|
||||
// away
|
||||
buf[0] = @intCast(width);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test CodepointWidth {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
@@ -8,6 +8,7 @@ const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Benchmark = @import("Benchmark.zig");
|
||||
const options = @import("options.zig");
|
||||
const uucode = @import("uucode");
|
||||
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
|
||||
const unicode = @import("../unicode/main.zig");
|
||||
|
||||
@@ -38,6 +39,9 @@ pub const Mode = enum {
|
||||
|
||||
/// Ghostty's table-based approach.
|
||||
table,
|
||||
|
||||
/// Uucode
|
||||
uucode,
|
||||
};
|
||||
|
||||
/// Create a new terminal stream handler for the given arguments.
|
||||
@@ -60,6 +64,7 @@ pub fn benchmark(self: *GraphemeBreak) Benchmark {
|
||||
.stepFn = switch (self.opts.mode) {
|
||||
.noop => stepNoop,
|
||||
.table => stepTable,
|
||||
.uucode => stepUucode,
|
||||
},
|
||||
.setupFn = setup,
|
||||
.teardownFn = teardown,
|
||||
@@ -134,6 +139,160 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
|
||||
}
|
||||
}
|
||||
|
||||
const GraphemeBoundaryClass = uucode.TypeOfX(.grapheme_boundary_class);
|
||||
|
||||
pub fn computeGraphemeBoundaryClass(
|
||||
gb1: GraphemeBoundaryClass,
|
||||
gb2: GraphemeBoundaryClass,
|
||||
state: *uucode.grapheme.BreakState,
|
||||
) bool {
|
||||
// Set state back to default when `gb1` or `gb2` is not expected in sequence.
|
||||
switch (state.*) {
|
||||
.regional_indicator => {
|
||||
if (gb1 != .regional_indicator or gb2 != .regional_indicator) {
|
||||
state.* = .default;
|
||||
}
|
||||
},
|
||||
.extended_pictographic => {
|
||||
switch (gb1) {
|
||||
.extend,
|
||||
.zwj,
|
||||
.extended_pictographic,
|
||||
=> {},
|
||||
|
||||
else => state.* = .default,
|
||||
}
|
||||
|
||||
switch (gb2) {
|
||||
.extend,
|
||||
.zwj,
|
||||
.extended_pictographic,
|
||||
=> {},
|
||||
|
||||
else => state.* = .default,
|
||||
}
|
||||
},
|
||||
.default, .indic_conjunct_break_consonant, .indic_conjunct_break_linker => {},
|
||||
}
|
||||
|
||||
// GB6: L x (L | V | LV | VT)
|
||||
if (gb1 == .L) {
|
||||
if (gb2 == .L or
|
||||
gb2 == .V or
|
||||
gb2 == .LV or
|
||||
gb2 == .LVT) return false;
|
||||
}
|
||||
|
||||
// GB7: (LV | V) x (V | T)
|
||||
if (gb1 == .LV or gb1 == .V) {
|
||||
if (gb2 == .V or gb2 == .T) return false;
|
||||
}
|
||||
|
||||
// GB8: (LVT | T) x T
|
||||
if (gb1 == .LVT or gb1 == .T) {
|
||||
if (gb2 == .T) return false;
|
||||
}
|
||||
|
||||
// Handle GB9 (Extend | ZWJ) later, since it can also match the start of
|
||||
// GB9c (Indic) and GB11 (Emoji ZWJ)
|
||||
|
||||
// GB9a: SpacingMark
|
||||
if (gb2 == .spacing_mark) return false;
|
||||
|
||||
// GB9b: Prepend
|
||||
if (gb1 == .prepend) return false;
|
||||
|
||||
// GB11: Emoji ZWJ sequence
|
||||
if (gb1 == .extended_pictographic) {
|
||||
// start of sequence:
|
||||
|
||||
// In normal operation, we'll be in this state, but
|
||||
// precomputeGraphemeBreak iterates all states.
|
||||
// std.debug.assert(state.* == .default);
|
||||
|
||||
if (gb2 == .extend or gb2 == .zwj) {
|
||||
state.* = .extended_pictographic;
|
||||
return false;
|
||||
}
|
||||
// else, not an Emoji ZWJ sequence
|
||||
} else if (state.* == .extended_pictographic) {
|
||||
// continue or end sequence:
|
||||
|
||||
if (gb1 == .extend and (gb2 == .extend or gb2 == .zwj)) {
|
||||
// continue extend* ZWJ sequence
|
||||
return false;
|
||||
} else if (gb1 == .zwj and gb2 == .extended_pictographic) {
|
||||
// ZWJ -> end of sequence
|
||||
state.* = .default;
|
||||
return false;
|
||||
} else {
|
||||
// Not a valid Emoji ZWJ sequence
|
||||
state.* = .default;
|
||||
}
|
||||
}
|
||||
|
||||
// GB12 and GB13: Regional Indicator
|
||||
if (gb1 == .regional_indicator and gb2 == .regional_indicator) {
|
||||
if (state.* == .default) {
|
||||
state.* = .regional_indicator;
|
||||
return false;
|
||||
} else {
|
||||
state.* = .default;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// GB9: x (Extend | ZWJ)
|
||||
if (gb2 == .extend or gb2 == .zwj) return false;
|
||||
|
||||
// GB999: Otherwise, break everywhere
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn isBreak(
|
||||
cp1: u21,
|
||||
cp2: u21,
|
||||
state: *uucode.grapheme.BreakState,
|
||||
) bool {
|
||||
const table = comptime uucode.grapheme.precomputeGraphemeBreak(
|
||||
GraphemeBoundaryClass,
|
||||
computeGraphemeBoundaryClass,
|
||||
);
|
||||
const gb1 = uucode.getX(.grapheme_boundary_class, cp1);
|
||||
const gb2 = uucode.getX(.grapheme_boundary_class, cp2);
|
||||
const result = table.get(gb1, gb2, state.*);
|
||||
state.* = result.state;
|
||||
return result.result;
|
||||
}
|
||||
|
||||
fn stepUucode(ptr: *anyopaque) Benchmark.Error!void {
|
||||
const self: *GraphemeBreak = @ptrCast(@alignCast(ptr));
|
||||
|
||||
const f = self.data_f orelse return;
|
||||
var r = std.io.bufferedReader(f.reader());
|
||||
var d: UTF8Decoder = .{};
|
||||
var state: uucode.grapheme.BreakState = .default;
|
||||
var cp1: u21 = 0;
|
||||
var buf: [4096]u8 = undefined;
|
||||
while (true) {
|
||||
const n = r.read(&buf) catch |err| {
|
||||
log.warn("error reading data file err={}", .{err});
|
||||
return error.BenchmarkFailed;
|
||||
};
|
||||
if (n == 0) break; // EOF reached
|
||||
|
||||
for (buf[0..n]) |c| {
|
||||
const cp_, const consumed = d.next(c);
|
||||
assert(consumed);
|
||||
if (cp_) |cp2| {
|
||||
const v = isBreak(cp1, @intCast(cp2), &state);
|
||||
buf[0] = @intCast(@intFromBool(v));
|
||||
cp1 = cp2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test GraphemeBreak {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
Reference in New Issue
Block a user