benchmark: add dense style replacement workload

Add a replacement mode that keeps all but one requested style live
while repeatedly adding and releasing unique styles. This isolates
dense backward-shift deletion and ID reuse without triggering the
all-dead bulk reset.

Generate replacement styles on demand so long runs continue to
exercise misses instead of cycling through the resident working set.
This commit is contained in:
Mitchell Hashimoto
2026-07-11 20:10:02 -07:00
parent 3267b63ef9
commit 5f16939905

View File

@@ -18,6 +18,7 @@ opts: Options,
page: terminal.Page,
values: []style.Style,
ids: []style.Id,
cursor: u24 = 0x100000,
pub const Options = struct {
/// Number of distinct styles in the working set.
@@ -37,13 +38,29 @@ pub const Mode = enum {
/// Rebuild a working set whose previous entries are all dead, then clear
/// all of its references again.
churn,
/// Keep the set nearly full while repeatedly adding and releasing unique
/// styles. This exercises dense deletion without the all-dead reset.
replace,
};
fn styleFor(n: u24) style.Style {
return .{ .fg_color = .{ .rgb = .{
.r = @truncate(n),
.g = @truncate(n >> 8),
.b = @truncate(n >> 16),
} } };
}
pub fn create(alloc: Allocator, opts: Options) !*StyleSet {
if (opts.entries == 0 or opts.entries > style.Set.max_count) {
log.err("entries must be between 1 and {}", .{style.Set.max_count});
return error.InvalidEntries;
}
if (opts.mode == .replace and opts.entries < 2) {
log.err("replace mode requires at least 2 entries", .{});
return error.InvalidEntries;
}
const ptr = try alloc.create(StyleSet);
errdefer alloc.destroy(ptr);
@@ -62,20 +79,28 @@ pub fn create(alloc: Allocator, opts: Options) !*StyleSet {
errdefer alloc.free(ids);
for (values, 0..) |*value, i| {
const n: u24 = @intCast(i + 1);
value.* = .{ .fg_color = .{ .rgb = .{
.r = @truncate(n),
.g = @truncate(n >> 8),
.b = @truncate(n >> 16),
} } };
value.* = styleFor(@intCast(i + 1));
}
for (values, ids) |value, *id| {
id.* = try page.styles.add(page.memory, value);
}
if (opts.mode == .churn) {
for (ids) |id| page.styles.release(page.memory, id);
switch (opts.mode) {
.lookup, .churn => {
for (values, ids) |value, *id| {
id.* = try page.styles.add(page.memory, value);
}
if (opts.mode == .churn) {
for (ids) |id| page.styles.release(page.memory, id);
}
},
.replace => {
// Leave one slot for the unique style added by stepReplace while
// keeping the table dense enough to exercise deletion backshifts.
for (
values[0 .. values.len - 1],
ids[0 .. ids.len - 1],
) |value, *id| {
id.* = try page.styles.add(page.memory, value);
}
},
}
ptr.* = .{
@@ -99,10 +124,27 @@ pub fn benchmark(self: *StyleSet) Benchmark {
.stepFn = switch (self.opts.mode) {
.lookup => stepLookup,
.churn => stepChurn,
.replace => stepReplace,
},
});
}
fn stepReplace(ptr: *anyopaque) Benchmark.Error!void {
const self: *StyleSet = @ptrCast(@alignCast(ptr));
for (0..self.opts.loops) |_| {
for (self.values) |_| {
const id = self.page.styles.add(
self.page.memory,
styleFor(self.cursor),
) catch return error.BenchmarkFailed;
std.mem.doNotOptimizeAway(id);
self.page.styles.release(self.page.memory, id);
self.cursor +%= 1;
}
}
}
fn stepLookup(ptr: *anyopaque) Benchmark.Error!void {
const self: *StyleSet = @ptrCast(@alignCast(ptr));
@@ -131,7 +173,7 @@ fn stepChurn(ptr: *anyopaque) Benchmark.Error!void {
test StyleSet {
const alloc = std.testing.allocator;
inline for (.{ Mode.lookup, Mode.churn }) |mode| {
inline for (.{ Mode.lookup, Mode.churn, Mode.replace }) |mode| {
const impl = try StyleSet.create(alloc, .{
.entries = 64,
.mode = mode,