-
-
Notifications
You must be signed in to change notification settings - Fork 161
perf(gc): birth a large JSON result in the arena when the young generation already holds a document #10177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
perf(gc): birth a large JSON result in the arena when the young generation already holds a document #10177
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ### perf(gc): birth a large JSON result in the arena when the young generation already holds a document | ||
|
|
||
| JSON results at or above 512 KB are malloc-tracked so the next minor can reclaim a discarded result cheaply. On a parse-then-stringify loop over a document-sized input that inverted: the non-empty malloc registry forbids the untraced in-place promotion, so every minor traced the whole freshly parsed tree (55 ms for 20 MB, half the roundtrip's wall time) to reclaim one leaf. Such a leaf is now born old in the arena when the young generation holds at least the leaf's bytes and a base nursery's worth of data and was not measured as dying, and the next trigger decision gives the nursery minor one-time priority over old-reclaim while the young generation is unmeasured, so a stringify-only loop still promotes its input once and then keeps the malloc path. Measured on `records_array_20m:roundtrip`: CPU 206.8 → 114.1 ms (0.55×, now ahead of the better of Node 26.5.1 / Bun 1.3.14 at 162.1) and peak RSS 295 → 266 MiB (Node 261); 20 MB stringify rows −40 MB peak RSS at flat CPU; every other JSON row within noise (#10169). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| //! #10169: a document-sized JSON leaf born old under young pressure gives the | ||
| //! nursery minor one-time priority over old-reclaim, and only while the young | ||
| //! generation is unmeasured. Both halves are asserted: the priority fires | ||
| //! exactly once per leaf, and a measured young generation buys none. | ||
|
|
||
| use super::super::policy::{ | ||
| gc_budgeted_due_trigger, note_young_leaf_born_old, BudgetedGcTrigger, | ||
| ScavengeNurseryCapTestGuard, GC_OLD_RECLAIM_PENDING, | ||
| }; | ||
| use super::super::*; | ||
| use super::support::*; | ||
|
|
||
| #[test] | ||
| fn young_leaf_born_old_prioritises_the_nursery_minor_until_measured() { | ||
| let _isolation = GcTestIsolationGuard::new(); | ||
| let _pacing = crate::gc::policy::force_moving_gc_pacing(); | ||
| let _triggers = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); | ||
| let _cap_due = ScavengeNurseryCapTestGuard::due_at_bytes(1); | ||
| // The isolated arena starts empty; one young allocation makes "due at one | ||
| // byte" actually due. | ||
| let filler = [b'y'; 64]; | ||
| crate::string::js_string_from_bytes(filler.as_ptr(), filler.len() as u32); | ||
| assert!( | ||
| crate::arena::copying_from_space_in_use_bytes() >= 1, | ||
| "fixture: the young generation must hold something for the cap to be due" | ||
| ); | ||
| let previous_survival = last_young_survival_permille(); | ||
| GC_OLD_RECLAIM_PENDING.with(|pending| pending.set(true)); | ||
| assert_eq!( | ||
| gc_budgeted_due_trigger(), | ||
| Some(BudgetedGcTrigger::OldReclaim), | ||
| "fixture: old-reclaim must be due before the leaf can outrank it" | ||
| ); | ||
|
|
||
| // Unmeasured young generation: the leaf buys the nursery minor exactly one | ||
| // decision, then old-reclaim is back. | ||
| clear_young_survival_for_tests(); | ||
| note_young_leaf_born_old(); | ||
| assert_eq!( | ||
| gc_budgeted_due_trigger(), | ||
| Some(BudgetedGcTrigger::YoungScavengeCap) | ||
| ); | ||
| assert_eq!( | ||
| gc_budgeted_due_trigger(), | ||
| Some(BudgetedGcTrigger::OldReclaim) | ||
| ); | ||
|
|
||
| // Measured as retained: no priority, and the flag is still consumed. | ||
| seed_young_survival_for_tests(999); | ||
| note_young_leaf_born_old(); | ||
| assert_eq!( | ||
| gc_budgeted_due_trigger(), | ||
| Some(BudgetedGcTrigger::OldReclaim) | ||
| ); | ||
| clear_young_survival_for_tests(); | ||
| assert_eq!( | ||
| gc_budgeted_due_trigger(), | ||
| Some(BudgetedGcTrigger::OldReclaim), | ||
| "a consumed flag must not be honoured later" | ||
| ); | ||
|
|
||
| GC_OLD_RECLAIM_PENDING.with(|pending| pending.set(false)); | ||
| match previous_survival { | ||
| Some(permille) => seed_young_survival_for_tests(permille), | ||
| None => clear_young_survival_for_tests(), | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Gate completion accounting on
malloc_tracked.json_output_storage_allocreturnsfalsewhenjson_leaf_prefers_arenaselects arena storage (crates/perry-runtime/src/string/mod.rs:697-711). The parser reaches this path for borrowed values and keys, butstring_from_json_bytesdiscards the flag and unconditionally callsnote_completed_malloc_json_outputfor every large leaf. That function adds malloc debt and schedules a sweep at 32 MiB. Keepmalloc_trackedin scope and call it only when the flag istrue, as the other callers do.🤖 Prompt for AI Agents