Skip to content

Commit 325a12b

Browse files
committed
perf(gc): collect precise nursery batches
Reclaim private young generations at a 2 MiB cache-local floor only when the VM explicitly materializes every managed root. Conservative, concurrent, and shared checkpoints retain the established 4 MiB cadence. Add a direct collection oracle, isolate compaction placement tests from automatic nursery policy, and retain the bounded conservative-checkpoint workload. Focused Debug and TSan coverage passes with exact object-churn checksums. Refs #44.
1 parent b22fa64 commit 325a12b

1 file changed

Lines changed: 37 additions & 1 deletion

File tree

src/context.zig

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const finalization_cleanup_queue_reserve_granularity = 16;
3838
const module_queue_reserve_granularity = 16;
3939
const module_namespace_waiter_reserve_granularity = 16;
4040
const runtime_gc_tenuring_age: u8 = 3;
41+
const precise_gc_nursery_threshold_bytes: usize = 2 * 1024 * 1024;
4142
const gc_auto_compaction_min_reclaimable_bytes: usize = 512 * 1024;
4243

4344
/// Serializes calls into an embedder allocator that may not itself be safe for
@@ -6252,8 +6253,14 @@ pub const Context = struct {
62526253
/// A full or concurrent cycle already in progress keeps ownership of the checkpoint.
62536254
fn collectYoungMidScriptIfNeeded(self: *Context, h: *GcHeap, machine: *interp.Interpreter) bool {
62546255
if (h.marking.load(.acquire) or h.concurrent.load(.acquire)) return false;
6255-
if (!h.shouldCollectYoung()) return false;
62566256
const precise_roots = machine.gc_precise_safepoint;
6257+
// Exact single-mutator checkpoints have no conservative false roots,
6258+
// so reclaim a cache-local young batch before the wider general nursery
6259+
// threshold. Generic, concurrent, and shared checkpoints keep zig-gc's
6260+
// established cadence to avoid repeated promotion of ambiguous roots.
6261+
const precise_cache_batch =
6262+
precise_roots and !h.parallel and h.young_bytes >= precise_gc_nursery_threshold_bytes;
6263+
if (!h.shouldCollectYoung() and !precise_cache_batch) return false;
62576264
self.gc_scan_native_stack = !precise_roots;
62586265
defer self.gc_scan_native_stack = false;
62596266
self.gc_scan_parked_stacks = !precise_roots;
@@ -16245,6 +16252,9 @@ test "GC compaction preserves quiescent pointer-free baseline JIT tiers" {
1624516252
.enable_jit = true,
1624616253
});
1624716254
defer ctx.destroy();
16255+
// This test constructs tail fragmentation for an explicit full compaction;
16256+
// keep automatic nursery policy from relocating its witness first.
16257+
ctx.gc.?.nursery_threshold_bytes = std.math.maxInt(usize);
1624816258

1624916259
// Keep dead Object and Function cells live until the next evaluation has
1625016260
// started. The target function and witness are then allocated behind them,
@@ -16320,6 +16330,9 @@ test "GC requested compaction resumes the same baseline tier from a precise nati
1632016330
.enable_jit = true,
1632116331
});
1632216332
defer ctx.destroy();
16333+
// Preserve the deliberate pre-compaction placement independently of the
16334+
// production nursery threshold.
16335+
ctx.gc.?.nursery_threshold_bytes = std.math.maxInt(usize);
1632316336

1632416337
_ = try ctx.evaluate(
1632516338
\\globalThis.preciseDiscardFunctions = [];
@@ -18234,6 +18247,29 @@ test "enable_gc: mid-script collection reclaims garbage during a running loop (b
1823418247
try std.testing.expect(ctx.gc.?.live_cells < 20000);
1823518248
}
1823618249

18250+
test "enable_gc: precise checkpoints reclaim a cache-local nursery batch" {
18251+
const ctx = try Context.createWith(std.testing.allocator, .{ .enable_gc = true });
18252+
defer ctx.destroy();
18253+
const heap = ctx.gc.?;
18254+
try std.testing.expect(heap.nursery_threshold_bytes > precise_gc_nursery_threshold_bytes);
18255+
18256+
const saved = gc_mod.setActiveContext(ctx);
18257+
defer gc_mod.restoreActiveContext(saved);
18258+
while (heap.young_bytes < precise_gc_nursery_threshold_bytes) {
18259+
const object = try heap.create(value.Object, .object);
18260+
object.* = .{};
18261+
object.initInlineSlots();
18262+
}
18263+
try std.testing.expect(!heap.shouldCollectYoung());
18264+
18265+
var machine = ctx.interpreter();
18266+
machine.gc_precise_safepoint = true;
18267+
const minor_before = heap.minor_collections;
18268+
try std.testing.expect(ctx.collectYoungMidScriptIfNeeded(heap, &machine));
18269+
try std.testing.expectEqual(minor_before + 1, heap.minor_collections);
18270+
try std.testing.expect(heap.young_bytes < precise_gc_nursery_threshold_bytes);
18271+
}
18272+
1823718273
test "enable_gc: mid-script safepoints collect nursery before old heap" {
1823818274
const ctx = try Context.createWith(std.testing.allocator, .{ .enable_gc = true });
1823918275
defer ctx.destroy();

0 commit comments

Comments
 (0)