Skip to content

Commit 14d94ec

Browse files
committed
perf(gc): skip empty object finalizers
Return immediately for owner-only plain inline objects with no storage sidecar, avoiding realm resolution and six empty resource probes. Shared realms and finalizer telemetry retain exact routing and accounting. Five repeated object-churn comparisons put steady 4/8-lane medians at 124.2/207.6 ms versus JSC 146.0/217.5 ms. The focused skip/telemetry oracle passes in Debug and ThreadSanitizer builds. Refs #44.
1 parent 4b48df1 commit 14d94ec

1 file changed

Lines changed: 49 additions & 0 deletions

File tree

‎src/gc.zig‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const StringCell = strcell.StringCell;
4040

4141
var object_batch_cells_for_testing: std.atomic.Value(u64) = .init(0);
4242
var relocation_verifications_for_testing: std.atomic.Value(u64) = .init(0);
43+
var plain_object_finalizer_skips_for_testing: std.atomic.Value(u64) = .init(0);
4344

4445
pub fn objectBatchCellsForTesting() u64 {
4546
return object_batch_cells_for_testing.load(.monotonic);
@@ -49,6 +50,10 @@ pub fn relocationVerificationsForTesting() u64 {
4950
return relocation_verifications_for_testing.load(.monotonic);
5051
}
5152

53+
pub fn plainObjectFinalizerSkipsForTesting() u64 {
54+
return plain_object_finalizer_skips_for_testing.load(.monotonic);
55+
}
56+
5257
/// The engine's GC cell taxonomy. Each `Heap.create(T, kind)` tags its cell so
5358
/// `trace`/`finalize` dispatch without RTTI. AST nodes, bytecode chunks, and
5459
/// `Shape`s are immutable and arena-permanent — they are *not* GC cells and
@@ -3330,6 +3335,21 @@ pub const Binding = struct {
33303335
/// slabs individually. A SharedArrayBuffer wrapper owns one realm retain
33313336
/// that must be released when the wrapper cell dies.
33323337
pub fn finalize(self: *Binding, cell: *anyopaque, kind: Kind) void {
3338+
if (kind == .object) {
3339+
const state = self.context.gc_state orelse return;
3340+
const object: *Object = @ptrCast(@alignCast(cell));
3341+
// An owner-only plain inline object has no side allocation, host
3342+
// owner, external resource, or rare state to release. Skip realm
3343+
// routing and six empty sidecar probes unless test telemetry needs
3344+
// the per-kind count. Shared heaps retain exact per-cell routing.
3345+
if (!state.realms.requiresCellRouting() and
3346+
self.context.gc_finalizer_stats_out == null and
3347+
object.storageState() == null)
3348+
{
3349+
if (builtin.is_test) _ = plain_object_finalizer_skips_for_testing.fetchAdd(1, .monotonic);
3350+
return;
3351+
}
3352+
}
33333353
const ctx = self.realmForCell(cell) orelse return;
33343354
if (ctx.gc_finalizer_stats_out) |stats| stats.addKind(kind);
33353355
switch (kind) {
@@ -3419,6 +3439,35 @@ pub const Binding = struct {
34193439
}
34203440
};
34213441

3442+
test "owner-only plain object finalization skips empty sidecar probes" {
3443+
const owner = try ContextMod.Context.createWith(std.testing.allocator, .{
3444+
.enable_gc = true,
3445+
.enable_jit = false,
3446+
});
3447+
defer owner.destroy();
3448+
owner.collectGarbage();
3449+
owner.gc.?.threshold_bytes = std.math.maxInt(usize);
3450+
owner.gc.?.nursery_threshold_bytes = std.math.maxInt(usize);
3451+
3452+
const skips_before = plainObjectFinalizerSkipsForTesting();
3453+
_ = try owner.evaluate(
3454+
\\for (var i = 0; i < 64; i = i + 1) ({ value: i, stamp: i & 7, previous: i - 1 });
3455+
);
3456+
owner.collectGarbage();
3457+
try std.testing.expect(plainObjectFinalizerSkipsForTesting() >= skips_before + 64);
3458+
3459+
var stats = ContextMod.Context.GcFinalizerStats{};
3460+
owner.gc_finalizer_stats_out = &stats;
3461+
defer owner.gc_finalizer_stats_out = null;
3462+
const skips_with_stats = plainObjectFinalizerSkipsForTesting();
3463+
_ = try owner.evaluate(
3464+
\\for (var i = 0; i < 64; i = i + 1) ({ value: i, stamp: i & 7, previous: i - 1 });
3465+
);
3466+
owner.collectGarbage();
3467+
try std.testing.expect(stats.objects >= 64);
3468+
try std.testing.expectEqual(skips_with_stats, plainObjectFinalizerSkipsForTesting());
3469+
}
3470+
34223471
test "precise heap realm registry traces relocates and retires one sibling exactly once" {
34233472
const owner = try ContextMod.Context.createWith(std.testing.allocator, .{ .enable_gc = true });
34243473
defer owner.destroy();

0 commit comments

Comments
 (0)