You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf(gc): right-size per-object footprint — INLINE_SLOT_FLOOR=4 reserves 4 slots for every object (#7469 Workstream B) #7714
Workstream B — the per-object footprint half of #7469, which was quantified but never implemented.
Workstream A is done and closed (#7565/#7566: TLS resolution count + the loop-site inline-bump gate, churn_alloc 1.81× at +0 bytes for non-loop sites), which exceeded its own ≥2× acceptance bar. B never started, and #7469 is being closed so its remaining scope stops hiding behind a finished umbrella.
What is actually left
crates/perry-runtime/src/object/mod.rs:24 still reads:
pub(crate)constINLINE_SLOT_FLOOR:usize = 4;
unchanged since the measurement. Every object allocation therefore reserves at least 4 slots regardless of the shape's real field count — js_object_alloc(0, 0) (the {} and FFI path) allocates 4 slots for zero fields.
The measurement to start from, and its ceiling
#7469's own footprint work measured 136.9 B/record = 84.4 B arena + 52.6 B OUTSIDE the arena. That out-of-arena half is the important number: it caps any purely-arena remedy at ~113 B/record, not the ~80 B a naive reading of the arena figure suggests. Anyone picking this up should re-derive both halves before designing, because a right-sizing change that only moves the 84.4 B cannot reach the target.
Two warnings from the last attempt
A #[cold] install split regressed — read the area's topic notes before coding.
Re-profiled before coding, as this issue instructs — and the headline number is much smaller than a slot count suggests.
The A/B
Same tree, one constant pair moved in lockstep (INLINE_SLOT_FLOOR 4→2 and its codegen mirror MIN_FIELD_SLOTS in lower_call/new_alloc.rs), perry-dev, archives rebuilt and mtime-verified, M1 Max laptop:
bench
floor=4
floor=2
delta
retain (3M × {a,b} — 2 fields, the best case for this change)
317.6 MB
308.9 MB
−8.7 MB (−2.7%)
retain_wide (≥4 fields, floor never binds)
445.7 MB
445.6 MB
−0.1 MB (0.0%)
churn_alloc
24.7 MB
24.7 MB
0.0
Output byte-identical on all three, and cargo test -p perry-runtime --lib is 2052 passed / 0 failed at floor=2 — so the corruption-critical invariant does hold when both constants move together, which is worth knowing before anyone designs the real change.
Why that is the interesting part
A slot calculation predicts far more. retain allocates 3,000,000 two-field records; at 8 bytes per slot, dropping two reserved slots should return 16 B/record ≈ 48 MB. Measured: 8.7 MB, i.e. 2.9 B/record — about 18% of the theoretical figure.
The rest is absorbed before it reaches RSS: allocation lands in size classes, so a 2-slot and a 4-slot object of this shape frequently occupy the same bucket, and shrinking the reservation does not shrink the bucket. That is the same shape as this issue's own warning about the 52.6 B/record that lives outside the arena capping any arena-only remedy — the ceiling is lower again than that warning implies, because part of the arena half is not recoverable either.
What this means for the workstream
Right-sizing the floor is not, on this evidence, a footprint lever worth a corruption-critical change on its own: 2.7% on the single most favourable workload, 0% on the two others, against a constant whose comment says allocation, every bounds check and every direct-slot read must move in lockstep — plus a gc_ratchet re-pin, since the footprint cells move.
If Workstream B is worth continuing, the measurement says to aim at the size-class boundary rather than at the reservation: an allocation that returns a smaller bucket is where the 48 MB actually is. I have not implemented that; it is a different change from the one this issue describes, and it should be scoped from a fresh profile of the allocator's size classes rather than from INLINE_SLOT_FLOOR.
Leaving this open, rescoped by the numbers above rather than closed, since the underlying goal (per-object footprint) is real — only the named lever is weaker than it looks.
Fixed by merged PR #7928. Current origin/main has INLINE_SLOT_FLOOR = 2 in both the runtime and codegen target layout, with equality guards, exact allocation-footprint coverage, overflow-growth behavior coverage, and the requested corpus/GC validation recorded in the PR and changelog.d/7928-inline-slot-floor.md.
The separate residual work is already scoped correctly elsewhere: #7929 covers the byte-budget/object-cost GC interaction discovered by this change, while #7916 retains the broader object-header footprint work. This ticket's named Workstream B floor change is complete.
Activity
docs(changelog): fragment for the moving-loop poll default (#7714)
fix(gc): make the moving-loop poll default ON in the code, not just t…
proggeramlug commentedon Aug 10, 2026
Re-profiled before coding, as this issue instructs — and the headline number is much smaller than a slot count suggests.
The A/B
Same tree, one constant pair moved in lockstep (
INLINE_SLOT_FLOOR4→2 and its codegen mirrorMIN_FIELD_SLOTSinlower_call/new_alloc.rs),perry-dev, archives rebuilt and mtime-verified, M1 Max laptop:retain(3M ×{a,b}— 2 fields, the best case for this change)retain_wide(≥4 fields, floor never binds)churn_allocOutput byte-identical on all three, and
cargo test -p perry-runtime --libis 2052 passed / 0 failed at floor=2 — so the corruption-critical invariant does hold when both constants move together, which is worth knowing before anyone designs the real change.Why that is the interesting part
A slot calculation predicts far more.
retainallocates 3,000,000 two-field records; at 8 bytes per slot, dropping two reserved slots should return 16 B/record ≈ 48 MB. Measured: 8.7 MB, i.e. 2.9 B/record — about 18% of the theoretical figure.The rest is absorbed before it reaches RSS: allocation lands in size classes, so a 2-slot and a 4-slot object of this shape frequently occupy the same bucket, and shrinking the reservation does not shrink the bucket. That is the same shape as this issue's own warning about the 52.6 B/record that lives outside the arena capping any arena-only remedy — the ceiling is lower again than that warning implies, because part of the arena half is not recoverable either.
What this means for the workstream
Right-sizing the floor is not, on this evidence, a footprint lever worth a corruption-critical change on its own: 2.7% on the single most favourable workload, 0% on the two others, against a constant whose comment says allocation, every bounds check and every direct-slot read must move in lockstep — plus a
gc_ratchetre-pin, since the footprint cells move.If Workstream B is worth continuing, the measurement says to aim at the size-class boundary rather than at the reservation: an allocation that returns a smaller bucket is where the 48 MB actually is. I have not implemented that; it is a different change from the one this issue describes, and it should be scoped from a fresh profile of the allocator's size classes rather than from
INLINE_SLOT_FLOOR.Leaving this open, rescoped by the numbers above rather than closed, since the underlying goal (per-object footprint) is real — only the named lever is weaker than it looks.
perf(runtime): right-size small objects — INLINE_SLOT_FLOOR 4 -> 2 (#…
proggeramlug commentedon Aug 12, 2026
Fixed by merged PR #7928. Current
origin/mainhasINLINE_SLOT_FLOOR = 2in both the runtime and codegen target layout, with equality guards, exact allocation-footprint coverage, overflow-growth behavior coverage, and the requested corpus/GC validation recorded in the PR andchangelog.d/7928-inline-slot-floor.md.The separate residual work is already scoped correctly elsewhere: #7929 covers the byte-budget/object-cost GC interaction discovered by this change, while #7916 retains the broader object-header footprint work. This ticket's named Workstream B floor change is complete.