-
-
Notifications
You must be signed in to change notification settings - Fork 161
perf(buffer): give the buffer-registry probe the set filter its window can no longer be #9828
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
proggeramlug
wants to merge
5
commits into
PerryTS:main
from
proggeramlug:perf/buffer-registry-addr-filter
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
46d04a2
perf(enum): for-in builds its shadow set only when a prototype level …
30cc161
docs(changelog): fragment for PR 9823
0dbe980
test(enum): record WHY the deep-chain test is shaped the way it is
8283677
perf(buffer): give the buffer-registry probe the set filter its windo…
da38560
docs(changelog): fragment for PR 9828
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,28 @@ | ||
| **`for-in` no longer allocates a heap string and a hash entry for every own | ||
| name at every prototype level** (#9823). | ||
|
|
||
| `js_for_in_keys_value` kept a `HashSet<String>` of every own name — enumerable | ||
| or not — at every level of the prototype chain, so that a name owned closer to | ||
| the receiver hides the same name further along it (ECMA-262 14.7.5, 12.6.4-2). | ||
| It built that set unconditionally, which meant materialising a second key array | ||
| per level (all own names, on top of the enumerable ones) and turning every name | ||
| at every level into an owned `String` purely so it could be hashed. | ||
|
|
||
| That set can only filter a level at or below the first prototype, and a level | ||
| that contributes no enumerable keys of its own never consults it. It is now | ||
| built on demand — at the moment a prototype level actually has an enumerable | ||
| key to filter — from exactly the levels already walked, so the emitted key | ||
| sequence is unchanged. | ||
|
|
||
| On the compiled claude-code TUI, one 400-character reply: **159,947 `String` | ||
| allocations and 159,947 hash inserts become zero**, and the key arrays | ||
| materialised per call halve from 4.00 to 2.00. Across 17,281 `for-in` loops in | ||
| that reply, **no key was emitted from a prototype level at all**, so the set | ||
| that cost all of that filtered nothing. The strings totalled 1.91 MB, which is | ||
| why an allocation-byte ranking never surfaced this: the cost was 160,000 | ||
| mallocs, memcpys, hashes and frees, not the bytes they held. The collection | ||
| schedule is unchanged (41 vs 43 copying minors, 46 vs 48 budgeted full-cycle | ||
| steps). | ||
|
|
||
| `PERRY_ENUM_DIAG=<path>` reports the counters above. `PERRY_FORIN_LAZY_SHADOW=0` | ||
| restores the eager set. |
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,32 @@ | ||
| **The buffer-registry probe stops answering "maybe" to three quarters of the | ||
| addresses it is asked about** (#9828). | ||
|
|
||
| `is_registered_buffer` guards its three registries with | ||
| `BUFFER_LIKE_ADDR_WINDOW`, a process-global min/max span, and the 98.0 % | ||
| rejection rate in its doc comment is measured on `claude-code --help` — a run | ||
| that registers **10** buffers. A streaming turn registers **213**, scattered | ||
| across a **527 MB** span, so `[lo, hi]` covers half a gigabyte of ordinary heap | ||
| and stops discriminating: on one 400-character reply, 34.6 million probes, of | ||
| which the window admits **73.63 %** to the out-of-line lookup, and **99.79 % of | ||
| those find nothing**. | ||
|
|
||
| The probe now consults `RegistryAddrFilter` behind the window — the set filter | ||
| added after #9272 for exactly this failure, where a registry's entries are | ||
| ordinary heap objects interleaved with everything else. Rejection goes from | ||
| 26.37 % to **96.46 %**, removing **24.25 million out-of-line calls per reply**, | ||
| each of which cost a thread-local resolution and a hash. True positives are | ||
| unchanged. | ||
|
|
||
| The saturation question that structure demands was answered before adopting it: | ||
| `RegistryAddrFilter` accrues bits per admission and never clears them, so a | ||
| high-churn set would degrade it into the state #9807 documented for the | ||
| per-object layout filter. Buffers are the opposite case — probing is hot, | ||
| registration is rare — and 213 cumulative admissions against 1,024 bits gives a | ||
| 10.0 % false-positive rate. `PERRY_BUFFER_DIAG` reports the occupancy, the | ||
| window bounds and the rejection rate so the question stays answerable. | ||
|
|
||
| In the profile, `is_registered_buffer_slow` falls from 169 to 25 leaf samples | ||
| (−85 %); its inline caller rises 96 to 123 as the filter's hashes move there, | ||
| so the pair falls 44 % overall. That is roughly half of the 3.19 % the profile | ||
| attributed to the slow path, and it is below the streaming rig's resolution, so | ||
| turn CPU is unchanged. |
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.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Label
live_maxas per-thread or aggregate it across threads.BUFFER_REGISTRYis thread-local, butBUF_LIVE_MAXis process-global. Each registration reports only the current thread's registry length, sobuffer_dumpcan under-report live buffers across runtime threads. Maintain a process-wide live count or label this field as a per-thread maximum.🤖 Prompt for AI Agents