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% |
This commit is contained in:
Mitchell Hashimoto
2026-07-11 16:42:10 -07:00
parent a797a97834
commit 9328b67201
3 changed files with 26 additions and 4 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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,
);