Skip to content

perf(json): count a compiled loop's whole-array materialization as traversal evidence - #10249

Closed
proggeramlug wants to merge 2 commits into
mainfrom
json/clone-traversal-evidence
Closed

proggeramlug wants to merge 2 commits into
mainfrom
json/clone-traversal-evidence

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Traversal feedback (#10150) learns that a program scans its parsed arrays from element reads in lazy_get_rooted, and once it has, eligible JSON.parse calls skip the lazy tape and parse eagerly. The element-shape loop clone (#10171) materializes a lazy array whole in its preheader through js_array_refresh_local_head before any element is read lazily, so a scan loop served by the clone never produced evidence: every parse built the tape and then materialized every record anyway. A symbolized sample of records_array_1m:scan on the bench mini put a third of the parse samples in json_tape::build_tape_into.

js_array_refresh_local_head now notes one flip's worth of evidence (traversal_feedback::note_compiled_materialization) the first time it materializes a lazy array (traversal_feedback::lazy_array_unmaterialized is read before the materialization, so an already-materialized array is never counted twice). Its emitters are all cold arms that run about once per receiver — the clone preheader and the guarded indexed-read repairs in expr/index.rs / expr/index_get/guarded_array.rs — so the added tracked-header probe is not on a hot path. Materializing the whole array is valid evidence whatever the loop does afterwards: the tape it was built from is wasted either way.

Measurement (quiet bench mini, M1, best of 3 interleaved rounds per engine, /usr/bin/time -l)

cell main CPU ms / peak MiB this change best of Node 26.5.1 / Bun 1.3.14
records_array_16k:scan 149.6 / 33 (1.11× CPU) 109.7 / 33 (0.82×) 134.2 / 61
records_array_1m:scan 180.4 / 66 (1.18×) 132.6 / 57 (0.87×) 153.0 / 83
records_array_8m:scan 171.5 / 189 122.6 / 164 170.8 / 133

All other 47 JSON matrix rows unchanged within noise in the same run. Stacked on #10220/#10241 (which reclaim dead promoted trees), records_array_8m:scan reaches 164.1 ms / 110 MiB (0.96× CPU, 0.83× RSS).

Validation

  • New array::refresh_traversal_evidence_tests: a plain array is never evidence; the first materialization of a lazy array is exactly one flip (score 2); a second refresh of the same array adds nothing. Sabotage-checked: removing the evidence call fails it (left: 0, right: 2).
  • RUST_TEST_THREADS=1 cargo test --release -p perry-runtime --lib: 3794 passed, 1 failed — gc::tests::heap_generation::a_free_or_move_outside_every_scope_is_caught_in_debug_builds, which only passes in debug builds and fails under --release on main too.
  • scripts/run_lint_gates.sh: main's pre-existing reds (public benchmark evidence freshness, -D warnings dead code in global_this_webassembly.rs, API docs drift); the file-size cap, which this change first tripped on array/header.rs, passes after moving the probe into traversal_feedback.rs (1996 lines).
  • Linked end to end on the bench mini (a compiled worker calls js_array_refresh_local_head), which is what caught an earlier revision that had moved #[no_mangle] onto the new helper.

Summary by CodeRabbit

  • Bug Fixes

    • Improved JSON parsing performance for workloads that scan large arrays.
    • Lazy arrays now provide traversal feedback when they are materialized, helping parsing choose a more efficient strategy.
    • Repeated access to an already materialized array no longer adds redundant feedback.
  • Tests

    • Added coverage verifying traversal feedback for lazy arrays and confirming unchanged behavior for regular arrays.

Ralph Küpper added 2 commits September 14, 2026 06:15
…aversal evidence

Traversal feedback (#10150) learns that a program scans its parsed arrays
from element reads in lazy_get_rooted. The element-shape loop clone (#10171)
materializes a lazy array whole in its preheader through
js_array_refresh_local_head before any element is read lazily, so a scan loop
served by the clone never produced evidence: every parse built the tape and
then materialized every record anyway. On the quiet bench mini
records_array_16k:scan and records_array_1m:scan read 1.11x and 1.18x the
better of Node and Bun, with a third of the parse samples in build_tape_into.

js_array_refresh_local_head now notes one flip's worth of evidence the first
time it materializes a lazy array. Its emitters are all cold arms that run
about once per receiver, so the added tracked-header probe is not on a hot path.
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: ba86b6e3-097e-4979-808c-1ce314211643

📥 Commits

Reviewing files that changed from the base of the PR and between eb13fa1 and 372af78.

📒 Files selected for processing (5)
  • changelog.d/10249-json-clone-traversal-evidence.md
  • crates/perry-runtime/src/array/header.rs
  • crates/perry-runtime/src/array/mod.rs
  • crates/perry-runtime/src/array/refresh_traversal_evidence_tests.rs
  • crates/perry-runtime/src/json/traversal_feedback.rs

Included review availability: Your plan provides up to 8 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

The runtime now counts the first compiled whole-array materialization of a lazy JSON array as traversal evidence. Tests verify the score changes once for lazy arrays and remains unchanged for plain arrays and repeated refreshes. The changelog records benchmark improvements.

Changes

JSON traversal evidence

Layer / File(s) Summary
Traversal feedback detection and scoring
crates/perry-runtime/src/json/traversal_feedback.rs
Traversal feedback now includes compiled lazy-array materialization. New helpers detect unmaterialized lazy arrays, add evidence, and expose the score for tests.
Array refresh integration and validation
crates/perry-runtime/src/array/header.rs, crates/perry-runtime/src/array/mod.rs, crates/perry-runtime/src/array/refresh_traversal_evidence_tests.rs, changelog.d/10249-json-clone-traversal-evidence.md
js_array_refresh_local_head records evidence after lazy-array materialization. Tests verify a score increase of 2 only on the first materialization. The changelog records the behavior and benchmark results.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 372af

This change makes the JSON runtime count the first whole-array materialization as traversal evidence, which lets scan-style workloads switch to eager parsing and reduces CPU time on large array benchmarks. No behavior or build problems were identified, so it appears ready to merge.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: counting compiled loop whole-array materialization as JSON traversal evidence.
Description check ✅ Passed The description is detailed and relevant. It explains the problem, implementation, performance impact, test coverage, validation results, and known baseline failures. It does not reproduce the templat…
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 4 files. (1 skipped: 1 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch json/clone-traversal-evidence

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Marked ready for review. The remaining hold was waiting on hosted CI, which is not a gate while the GitHub runners are down; the local validation this PR needs is already recorded above: full perry-runtime release suite (3,794 passed, the one failure is the debug-only a_free_or_move_outside_every_scope_is_caught_in_debug_builds, which fails under --release on main too), lint gates with only main's pre-existing reds, a sabotage-checked focused test, and the end-to-end link on the bench mini. Land order per #10241: this PR first, then #10220, then #10241.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Landed in merge train #10261: #10261. The merged main tree matches the validated train, and the fresh-head patch audit confirms the changes arrived.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant