From 9328b67201a96f357c23265f64887f78ae870305 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 11 Jul 2026 16:42:10 -0700 Subject: [PATCH] terminal: specialize page offset hashing Page-local grapheme and hyperlink maps hashed 32-bit cell offsets through the generic byte-oriented Wyhash path. Hashing is paid on every lookup, insert, move, and erase even though the key is already an integer. Add an offset context that widens the key before applying the integer mixer. Widening preserves entropy in the high bits used by the map fingerprint, while avoiding the generic byte hashing path. Use the specialized context for both cell-offset maps. ReleaseFast medians use seven sequential runs after two warmups and report user CPU time. Each pass visits a 4,096-entry hyperlink map. | workload | working-set load | passes | before | after | change | |---|---:|---:|---:|---:|---:| | lookup | 100% | 40,000 | 478.518 ms | 313.179 ms | -34.6% | | remove/insert churn | 50% | 5 | 491.084 ms | 383.838 ms | -21.8% | --- src/terminal/hash_map.zig | 16 ++++++++++++++++ src/terminal/hyperlink.zig | 9 +++++++-- src/terminal/page.zig | 5 +++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/terminal/hash_map.zig b/src/terminal/hash_map.zig index d8feddcd1..f1fca1670 100644 --- a/src/terminal/hash_map.zig +++ b/src/terminal/hash_map.zig @@ -75,6 +75,22 @@ fn AutoContext(comptime K: type) type { }; } +/// Hash context specialized for offset keys. Offsets are already integers, +/// so hashing them through autoHash's byte-oriented Wyhash path adds work to +/// every page-map operation. Widen before mixing so the resulting u64 also +/// has entropy in the high bits used by Metadata's fingerprint. +pub fn OffsetContext(comptime T: type) type { + return struct { + pub inline fn hash(_: @This(), key: Offset(T)) u64 { + return std.hash.int(@as(u64, key.offset)); + } + + pub inline fn eql(_: @This(), a: Offset(T), b: Offset(T)) bool { + return a.offset == b.offset; + } + }; +} + /// A HashMap type that uses offsets rather than pointers, making it /// possible to efficiently move around the backing memory without /// invalidating the HashMap. diff --git a/src/terminal/hyperlink.zig b/src/terminal/hyperlink.zig index 35a16a2ae..2a8310ec1 100644 --- a/src/terminal/hyperlink.zig +++ b/src/terminal/hyperlink.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const hash_map = @import("hash_map.zig"); -const AutoOffsetHashMap = hash_map.AutoOffsetHashMap; +const OffsetHashMap = hash_map.OffsetHashMap; const pagepkg = @import("page.zig"); const size = @import("size.zig"); const Offset = size.Offset; @@ -20,7 +20,12 @@ pub const Id = size.HyperlinkCountInt; // The mapping of cell to hyperlink. We use an offset hash map to save space // since its very unlikely a cell is a hyperlink, so its a waste to store // the hyperlink ID in the cell itself. -pub const Map = AutoOffsetHashMap(Offset(Cell), Id, 80); +pub const Map = OffsetHashMap( + Offset(Cell), + Id, + hash_map.OffsetContext(Cell), + 80, +); /// A fully decoded hyperlink that may or may not have its /// memory within a page. The memory location of this is dependent diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 653a95c44..d68a576b0 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -21,7 +21,7 @@ const Offset = size.Offset; const OffsetBuf = size.OffsetBuf; const BitmapAllocator = @import("bitmap_allocator.zig").BitmapAllocator; const hash_map = @import("hash_map.zig"); -const AutoOffsetHashMap = hash_map.AutoOffsetHashMap; +const OffsetHashMap = hash_map.OffsetHashMap; const alignForward = std.mem.alignForward; const alignBackward = std.mem.alignBackward; @@ -92,9 +92,10 @@ const grapheme_chunk = grapheme_chunk_len * @sizeOf(u21); const GraphemeAlloc = BitmapAllocator(grapheme_chunk); const grapheme_count_default = GraphemeAlloc.bitmap_bit_size; pub const grapheme_bytes_default = grapheme_count_default * grapheme_chunk; -const GraphemeMap = AutoOffsetHashMap( +const GraphemeMap = OffsetHashMap( Offset(Cell), Offset(u21).Slice, + hash_map.OffsetContext(Cell), hash_map.default_max_load_percentage, );