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
churn is the only losing row that already executes fewer instructions than Node and
still loses. Instruction ratio 0.99, yet it runs at 1.16x Node. Its IPC is the
lowest in the entire corpus — 13.35 G instr/s against ~19 typical for Perry rows. This
is a memory-stall / locality problem, and no amount of instruction removal will fix it.
Evidence
Measured on 8bfdac447:
Perry
Node
wall
0.1987 s
0.1710 s
instructions
2.65 G
2.68 G
peak RSS
20.9 MiB
85.7 MiB
So Perry does slightly less work, in a quarter of the memory, and still takes 16% longer.
92.4% perry_fn_churn_ts__chunk$spec_b <- user code, one function
3.6% js_string_addref_if_heap_string
3.2% value::addr_class::try_read_tracked_gc_header
0.5% _platform_memmove
0.3% gc::layout::layout_transfer
There is no runtime hot spot to remove — 92.4% is inside one compiled user function. The
cost is how that function's data is laid out and traversed, not how many instructions it runs.
What to investigate
This one needs hardware counters, not a sampling profiler. Suggested approach:
Get cache-miss and stall data (/usr/bin/time -l reports page faults; for L1/L2/LLC misses
use xctrace/Instruments' Counters template, or a Linux run under perf stat in the colima arm64 VM if that is easier — see the notes referenced in CLAUDE.md for the
aarch64-Linux repro path).
Check whether nursery allocation order matches traversal order for this workload, and
whether a copying minor is reordering survivors in a way that hurts locality.
churn's source is sweep-artifacts-0819b/sources/churn.ts; the hot function is chunk.
Why it matters beyond one row
churn_alloc (0.78x) and push_cls (0.80x) are the same workload family and already win, so
whatever churn does differently is isolatable by diffing it against those two. That is
probably the cheapest first step: find the one structural difference between churn and churn_alloc that costs 16%.
Acceptance
churn beats Node (< 1.00x), with a stated mechanism — a fix that moves the number without
an explanation of why will not survive the next layout change. No other row regressed, all
19 byte-exact, RSS not increased.
How to build and measure (shared setup)
Build with the static wrappers or you link a stale runtime and both A/B arms behave
identically — a vacuous result:
The benchmark corpus and Node-oracle expected output are staged at /Users/amlug/projects/perry/sweep-artifacts-0819b/ (sources/*.ts, expected/*.stdout).
Profiling: Perry strips its output binaries, so sample attributes everything to ???. Set PERRY_KEEP_SYMBOLS=1 PERRY_DEBUG_SYMBOLS=1 at compile time — the binary keeps
~1900 symbols and stays byte-exact — then read sample's "Sort by top of stack" section.
These programs are short; aggregate across ~14 runs for a usable sample count.
Constraints that apply to any fix here
Correctness first: all 19 corpus programs must stay byte-exact against their Node oracle.
Report wall time AND instructions retired AND peak RSS together. The project rule is
minimize RSS and keep best compute — never trade one for the other silently.
cargo test --release -p perry-runtime --lib, -p perry --bin perry, and bash scripts/run_lint_gates.sh (all 50) must pass.
Summary
churnis the only losing row that already executes fewer instructions than Node andstill loses. Instruction ratio 0.99, yet it runs at 1.16x Node. Its IPC is the
lowest in the entire corpus — 13.35 G instr/s against ~19 typical for Perry rows. This
is a memory-stall / locality problem, and no amount of instruction removal will fix it.
Evidence
Measured on
8bfdac447:So Perry does slightly less work, in a quarter of the memory, and still takes 16% longer.
Self-time profile (symbols kept, 14 runs aggregated):
There is no runtime hot spot to remove — 92.4% is inside one compiled user function. The
cost is how that function's data is laid out and traversed, not how many instructions it runs.
What to investigate
This one needs hardware counters, not a sampling profiler. Suggested approach:
/usr/bin/time -lreports page faults; for L1/L2/LLC missesuse
xctrace/Instruments' Counters template, or a Linux run underperf statin thecolimaarm64 VM if that is easier — see the notes referenced in CLAUDE.md for theaarch64-Linux repro path).
Relevant known facts: a 2-field object literal is 72 bytes in Perry, and the GC header is
40 bytes since perf(object): shrink common objects to 40 bytes #8313/perf(gc, codegen): recover the instruction cost of the 56 B → 48 B header shrink (#8122) #8204. Node reaches 85.7 MiB peak RSS here versus Perry's 20.9 MiB,
so Perry is denser — which makes the stall pattern more interesting, not less. It may be
allocation ordering / traversal order rather than object size.
whether a copying minor is reordering survivors in a way that hurts locality.
churn's source issweep-artifacts-0819b/sources/churn.ts; the hot function ischunk.Why it matters beyond one row
churn_alloc(0.78x) andpush_cls(0.80x) are the same workload family and already win, sowhatever
churndoes differently is isolatable by diffing it against those two. That isprobably the cheapest first step: find the one structural difference between
churnandchurn_allocthat costs 16%.Acceptance
churnbeats Node (< 1.00x), with a stated mechanism — a fix that moves the number withoutan explanation of why will not survive the next layout change. No other row regressed, all
19 byte-exact, RSS not increased.
How to build and measure (shared setup)
Build with the static wrappers or you link a stale runtime and both A/B arms behave
identically — a vacuous result:
The benchmark corpus and Node-oracle expected output are staged at
/Users/amlug/projects/perry/sweep-artifacts-0819b/(sources/*.ts,expected/*.stdout).Profiling: Perry strips its output binaries, so
sampleattributes everything to???. SetPERRY_KEEP_SYMBOLS=1 PERRY_DEBUG_SYMBOLS=1at compile time — the binary keeps~1900 symbols and stays byte-exact — then read
sample's "Sort by top of stack" section.These programs are short; aggregate across ~14 runs for a usable sample count.
Constraints that apply to any fix here
minimize RSS and keep best compute — never trade one for the other silently.
cargo test --release -p perry-runtime --lib,-p perry --bin perry, andbash scripts/run_lint_gates.sh(all 50) must pass.obviously guilty on this exact corpus and measured +0.2%.)
box::release_tests::the_intrusive_free_list_round_trips_a_whole_cohort(flaky: the_intrusive_free_list_round_trips_a_whole_cohort asserts a delta on a process-global counter under parallel tests #8401). If it fails once and reruns clean, it is not yours.