Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog.d/10371-gc-slot-iterator-memcpy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Performance

- **The GC slot visitor no longer copies a 152-byte iterator for every traced object (#10362).** `gc_child_slots` built a `HeapChildSlotIterator` per object, carrying a 40-byte `ShapeDescriptor` lifted out of the shape table (#8122). Two by-value moves turned it into an out-of-line `memcpy`: the Array/Closure arms' `Option::map(..).unwrap_or_else(..)` temporary, and `for .. in child_slots` in the masked arm. On a retained-object-graph workload that was 6,181,945 copies of exactly 152 bytes, and after this change it is 6.
The iterator now carries `ShapeRecordRef`, an 8-byte handle to the shape table's slab record (the iterator shrinks to 120 bytes). The Array/Closure arms build the iterator in place with `let-else`, and the masked arm iterates `&mut child_slots`.
Soundness: this is still ONE shape-table probe per receiver (#8122). The handle is read at the same points the lifted copy was (the carrier notes and the keys edge), before any slot is visited. It depends on the same record-address validity that `note_old_generation_carrier` already writes through (#9706: slab records never move, and a chunk is released only at the end of a major collection). The #8112 old-carrier/ephemeron gate and the #9726 full-trace note are unchanged.
Measured (instructions:u, exact counts): gc3 12.572G → 12.276G (−2.36%); smaller retained sets −0.78% to −1.97%; old→young churn −1.45%; allocation-only 0.00%.
The issue's profile had put memmove at 14% because `instructions:u` sampling skids on this CPU. Precise `cycles:pp` sampling shows 1.3%, which matches the measured win. Use a precise event for attribution and exact counters for totals.
28 changes: 28 additions & 0 deletions changelog.d/10381-gc-relocation-address-keyed-records.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
### Performance

### Performance

- **A moving collection no longer re-derives an object's layout header (#10362).** `layout_transfer`
runs for every evacuated object on every copying minor, both old-generation evacuations and
`js_array_grow`. All four callers copy the source header's `_reserved` into the destination
first, so the layout state, `GC_LAYOUT_ALL_POINTERS`, the raw-f64 / holes flags, the
element-shape bit and `GC_OBJ_TYPED_LAYOUT_INTACT` have already arrived — yet the funnel rewrote
those bits anyway, classified both headers, evaluated #7510's flag-and-filter gate twice, and
for every intact object re-resolved the intact bit through a ShapeId-keyed `SHAPE_LAYOUTS`
probe whose answer a relocation cannot change. Measured by single-stepping the #10362
retained-graph fixture: 160 instructions per moved array and 245 per moved object, 518M in all
(4.2% of the run), none of which reached a side-table record.
That contract is now stated and asserted, and the funnel moves only what a header cannot carry:
the element-shape record (#7480), the residual static-prototype registry (#9304) and the
per-object `TYPED_LAYOUTS` / `LAYOUT_SLOT_MASKS` entries (#7510) — each behind the bit or latch
that governs it, with the record moves in a `#[cold]` path entered on 0.05% of relocations.
The one behavioural change is that the lazy intact downgrade is gone: the bit is a fact of the
object and of tables a move does not touch, the state it cleared is legal and handled
(`shape_install_shared` leaves still-INTACT siblings to fall back, #8115 clears at the first
contradicting store, the trace falls back to scanning every slot), and an unmoved sibling keeps
its bit today. A new test builds a poisoned-shape intact receiver, moves it through a real
copying minor, and checks the bit, every query answer and the survival and rewrite of its child;
it fails on the parent commit and under a funnel that skips the per-object record move.
instructions:u, min of 5: gc3 12.276G -> 11.881G (-3.22%), retained-set variants -2.50% to
-4.88%, old->young churn -2.13%, allocation-only unchanged. 2,560,042 relocations on both arms.

12 changes: 1 addition & 11 deletions crates/perry-runtime/src/array/element_shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
//! | fast proof | `_reserved` bit 7 | `_reserved` bit 11 |
//! | rides a move | yes (`_reserved` is copied) | yes (same word) |
//! | self-heals by rescan | `ensure_array_numeric_raw_f64` | [`ensure_element_shape`] |
//! | move fixup | `transfer_array_numeric_layout` | [`transfer_element_shape`] |
//! | move fixup | none — the bit IS the record | [`transfer_element_shape`] |
//! | clear funnel | `clear_array_numeric_layout` | [`clear_element_shape`] |
//!
//! The one thing 4a does not need is a *payload*: "raw f64" is the whole
Expand Down Expand Up @@ -443,16 +443,6 @@ pub(crate) unsafe fn clear_element_shape(arr: *const ArrayHeader) {
bump_epoch();
}

/// Address-keyed sibling of [`clear_element_shape`], for the `layout_*`
/// family and other callers that hold a `usize`.
#[inline]
pub(crate) fn clear_element_shape_ptr(user_ptr: usize) {
if user_ptr == 0 {
return;
}
unsafe { clear_element_shape(user_ptr as *const ArrayHeader) }
}

/// Forget everything about an address, bit included. Used when an allocation
/// dies and its address may be recycled (`layout_clear_for_ptr`).
pub(crate) fn forget_element_shape(user_ptr: usize) {
Expand Down
19 changes: 0 additions & 19 deletions crates/perry-runtime/src/array/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1307,25 +1307,6 @@ pub(crate) fn clear_array_numeric_layout_ptr(user_ptr: usize) {
}
}

#[inline]
pub(crate) fn transfer_array_numeric_layout(old_user: usize, new_user: usize) {
if old_user == 0 || new_user == 0 || old_user == new_user {
return;
}
unsafe {
if array_has_raw_f64_layout_flag(old_user as *const ArrayHeader) {
set_array_raw_f64_layout_flag(new_user as *const ArrayHeader);
} else if array_has_raw_f64_holes_flag(old_user as *const ArrayHeader) {
// #6011: relocation copies slot bits verbatim, so the verified
// raw-f64-or-holes invariant carries over to the new backing.
clear_array_raw_f64_layout_flag(new_user as *const ArrayHeader);
set_array_raw_f64_holes_flag(new_user as *const ArrayHeader);
} else {
clear_array_raw_f64_layout_flag(new_user as *const ArrayHeader);
}
}
}

#[inline]
pub(crate) unsafe fn array_numeric_layout(arr: *const ArrayHeader) -> Option<NumericArrayLayout> {
let arr = clean_arr_ptr(arr);
Expand Down
8 changes: 4 additions & 4 deletions crates/perry-runtime/src/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ pub use self::concat_reverse::{
js_array_fill_range, js_array_reverse, js_array_reverse_value,
};
pub(crate) use self::element_shape::{
clear_element_shape_ptr, forget_element_shape, invalidate_all_element_shapes,
note_element_store, prune_dead_element_shape_owners, transfer_element_shape,
forget_element_shape, invalidate_all_element_shapes, note_element_store,
prune_dead_element_shape_owners, transfer_element_shape,
};
pub use self::element_shape::{
js_array_element_shape_check, js_array_element_shape_class, js_array_element_shape_epoch,
Expand Down Expand Up @@ -290,8 +290,8 @@ pub(crate) use self::header::{
normalize_array_receiver, note_array_slot, note_array_slot_layout_only,
note_array_slot_resolved_flags, rebuild_array_layout, rebuild_array_layout_exact,
refresh_array_numeric_layout, replay_array_growth_write_barriers, set_array_numeric_layout,
store_array_slot, store_array_slot_resolved, transfer_array_numeric_layout,
typed_array_receiver, value_bits_to_number, NumericArrayLayout, MIN_ARRAY_CAPACITY,
store_array_slot, store_array_slot_resolved, typed_array_receiver, value_bits_to_number,
NumericArrayLayout, MIN_ARRAY_CAPACITY,
};
pub(crate) use self::named_props::{
array_has_named_properties_resolved, array_has_sparse_index_properties_resolved,
Expand Down
Loading
Loading