From b8458383c9fe0404f074f239305d87cc908a340b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 11 Jul 2026 19:55:05 -0700 Subject: [PATCH] terminal: reuse hashes across set insertion Compute a RefCountedSet value hash once and pass it through lookup and insertion. A miss previously hashed before probing and then repeated the same work when insertion began. The shared private lookup path also keeps zero-capacity contexts from being evaluated before their backing data exists. ReleaseFast timings are seven-run median user CPU times. | Workload | Before | After | Change | | --- | ---: | ---: | ---: | | 128 styles, live lookup | 0.75s | 0.69s | -8.0% | | 128 styles, dead churn | 0.79s | 0.72s | -8.9% | | 4,096 styles, dead churn | 0.85s | 0.77s | -9.4% | | Alternating SGR stream | 1.13s | 1.11s | -1.8% | | Clear/redraw SGR stream | 0.67s | 0.65s | -3.0% | --- src/terminal/ref_counted_set.zig | 67 +++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/src/terminal/ref_counted_set.zig b/src/terminal/ref_counted_set.zig index 9c694dc02..c32723d01 100644 --- a/src/terminal/ref_counted_set.zig +++ b/src/terminal/ref_counted_set.zig @@ -271,6 +271,11 @@ pub fn RefCountedSet( return try self.addContext(base, value, self.context); } pub fn addContext(self: *Self, base: anytype, value: T, ctx: Context) AddError!Id { + if (self.layout.table_cap == 0) { + @branchHint(.cold); + return AddError.OutOfMemory; + } + const items = self.items.ptr(base); // Trim dead items from the end of the list. @@ -279,8 +284,10 @@ pub fn RefCountedSet( self.deleteItem(base, self.next_id, ctx); } + const hash: u64 = ctx.hash(value); + // If the item already exists, return it. - if (self.lookupContext(base, value, ctx)) |id| { + if (self.lookupAdaptedHash(base, value, ctx, hash)) |id| { // Notify the context that the value is "deleted" because // we're reusing the existing value in the set. This allows // callers to clean up any resources associated with the value. @@ -320,7 +327,7 @@ pub fn RefCountedSet( return AddError.OutOfMemory; } - const id = self.insert(base, value, self.next_id, ctx); + const id = self.insert(base, value, self.next_id, ctx, hash); items[id].meta.ref += 1; assert(items[id].meta.ref == 1); self.living += 1; @@ -536,20 +543,26 @@ pub fn RefCountedSet( /// Look up a value using a key and context whose hash and equality /// functions adapt that key to resident values of type T. pub fn lookupAdapted(self: *const Self, base: anytype, value: anytype, ctx: anytype) ?Id { - // A zero-capacity set (a valid special case of Layout.init) - // contains nothing and has a zero-size table, so we can't - // probe it: table[0] would read whatever memory follows the - // set in the backing buffer. + // A zero-capacity set has no table to probe. Check before hashing + // because adapted contexts may require initialized backing data. if (self.layout.table_cap == 0) { @branchHint(.cold); return null; } + return self.lookupAdaptedHash(base, value, ctx, ctx.hash(value)); + } + + fn lookupAdaptedHash( + self: *const Self, + base: anytype, + value: anytype, + ctx: anytype, + hash: u64, + ) ?Id { const table = self.table.ptr(base); const items = self.items.ptr(base); - const hash: u64 = ctx.hash(value); - for (0..self.max_psl + 1) |i| { const p: usize = @intCast((hash +% i) & self.layout.table_mask); const id = table[p]; @@ -591,8 +604,10 @@ pub fn RefCountedSet( /// be used as the ID. If an existing item is found, the `new_id` /// is ignored and the existing item's ID is returned. fn upsert(self: *Self, base: anytype, value: T, new_id: Id, ctx: Context) Id { + const hash: u64 = ctx.hash(value); + // If the item already exists, return it. - if (self.lookupContext(base, value, ctx)) |id| { + if (self.lookupAdaptedHash(base, value, ctx, hash)) |id| { // Notify the context that the value is "deleted" because // we're reusing the existing value in the set. This allows // callers to clean up any resources associated with the value. @@ -601,16 +616,23 @@ pub fn RefCountedSet( return id; } - return self.insert(base, value, new_id, ctx); + return self.insert(base, value, new_id, ctx, hash); } /// Insert the given value into the hash table with the given ID. /// /// If runtime safety is enabled, asserts that /// the value is not already present in the table. - fn insert(self: *Self, base: anytype, value: T, new_id: Id, ctx: Context) Id { + fn insert( + self: *Self, + base: anytype, + value: T, + new_id: Id, + ctx: Context, + hash: u64, + ) Id { if (comptime std.debug.runtime_safety) - assert(self.lookupContext(base, value, ctx) == null); + assert(self.lookupAdaptedHash(base, value, ctx, hash) == null); const table = self.table.ptr(base); const items = self.items.ptr(base); @@ -621,8 +643,6 @@ pub fn RefCountedSet( .meta = .{ .psl = 0, .ref = 0 }, }; - const hash: u64 = ctx.hash(value); - var held_id: Id = new_id; var held_item: *Item = &new_item; @@ -835,6 +855,25 @@ test "RefCountedSet random operations against live-value oracle" { } } +test "RefCountedSet zero capacity bypasses context" { + const testing = std.testing; + const Context = struct { + pub fn hash(_: *const @This(), _: u8) u64 { + unreachable; + } + + pub fn eql(_: *const @This(), _: u8, _: u8) bool { + unreachable; + } + }; + const Set = RefCountedSet(u8, u8, u16, Context); + + var buf: [0]u8 = .{}; + var set = Set.init(.init(&buf), .init(0), .{}); + try testing.expectEqual(null, set.lookup(&buf, 1)); + try testing.expectError(error.OutOfMemory, set.add(&buf, 1)); +} + test "RefCountedSet empty marker at maximum table capacity" { const testing = std.testing; const Context = struct {