Summary
Compiling a two-line file with a natively compiled tsc issues 79,691,777 is_registered_buffer probes in a 7.8 s run — roughly 10 million per second — to answer a question about a set that never holds more than 9 buffers. 90 of those probes are true positives.
is_registered_buffer_slow is 2.8% of leaf samples in that run.
Measured
Perry 0.5.1596 (fix/10656-codepointat-linear, i.e. with #10656/#10685 applied), macOS arm64, using the runtime's own PERRY_BUFFER_DIAG instrument:
[buffer-diag] probes=79691777 admits=26198956 (32.88 %) rejected=53492821 (67.12 %)
true_positives=90 (0.000344 % of admits)
window [0x5abfc600008, 0x5ac0204f938] span 90.3 MB
probed [0x5abf7a30030, 0x5ac216c9f48] span 668.6 MB -- window covers 13.5 % of the probed range
registrations=9 unregistrations=9 live_max=9
=> a 1024-bit/3-hash Bloom holding all admissions would be 0.0 % false-positive
Workload: ./tsc --noEmit demo.ts where demo.ts is two lines. Input file:
type Config = { port: number };
const cfg: Config = { port: "8080" };
Two distinct problems
1. The gate lets 26.2 M probes through to a 9-element hash set. BUFFER_LIKE_ADDR_WINDOW is doing real work — it rejects 67.12% inline, exactly as designed — but the surviving 32.88% each pay a function call, a thread-local resolution, a RefCell borrow and a hash lookup to discover, 99.999656% of the time, that the answer is no. The window spans 90.3 MB inside a 668.6 MB probed range, so a min/max bounding box simply cannot separate 9 addresses from the heap around them.
The instrument already names the fix and sizes it: a 1024-bit, 3-hash Bloom filter over registered addresses would be 0.0% false-positive on this workload — 128 bytes of state replacing 26.2 M hash probes. It fits the same soundness contract as the window (every writer arms it before publishing; a negative is authoritative, a positive falls through to the existing set).
2. The deeper question: why 79.7 M probes for 9 buffers? Nothing in this workload is buffer-shaped — it is a type-check of two lines of TypeScript. At ~10 M probes/second something on a very hot path is asking "is this a registered buffer?" about values that are overwhelmingly not buffers and never could be. is_registered_buffer has ~200 call sites per its own comment; the probe volume suggests one of them sits in a generic value/type ladder that runs per property access or per value touched.
Fixing (1) makes each probe ~free. Fixing (2) removes them. (2) is the larger win and is likely a small change once the caller is identified — a PERRY_BUFFER_DIAG variant that samples call-site backtraces would find it immediately.
Why this is worth doing
is_registered_buffer_slow is 2.8% of the run on its own, but it is one member of a family: predicates answering "what kind of pointer is this?" are 18.5% of the post-fix profile —
| symbol |
share |
is_registered_box_ptr |
5.2% |
is_registered_buffer_slow |
2.8% |
classify_heap_generation_uncached |
1.3% |
resolve_strategy_slow |
1.2% |
get_parent_class_id |
1.2% |
is_class_object_ptr |
1.1% |
keys_find_slot_by_bytes |
1.0% |
is_registered_map, classify_arena, … |
rest |
This one is the most clear-cut instance because the ratio is so extreme (90 hits in 79.7 M probes) and the runtime already ships the instrument that proves it.
Reproduction
PERRY_BUFFER_DIAG=/tmp/bufdiag.log ./tsc --noEmit demo.ts
tail -6 /tmp/bufdiag.log
Related: the same probe family is discussed in #10688's context; GC census recorded 147 MB of side tables against a 23 MB live heap, which is the memory face of this design.
Summary
Compiling a two-line file with a natively compiled
tscissues 79,691,777is_registered_bufferprobes in a 7.8 s run — roughly 10 million per second — to answer a question about a set that never holds more than 9 buffers. 90 of those probes are true positives.is_registered_buffer_slowis 2.8% of leaf samples in that run.Measured
Perry 0.5.1596 (
fix/10656-codepointat-linear, i.e. with #10656/#10685 applied), macOS arm64, using the runtime's ownPERRY_BUFFER_DIAGinstrument:Workload:
./tsc --noEmit demo.tswheredemo.tsis two lines. Input file:Two distinct problems
1. The gate lets 26.2 M probes through to a 9-element hash set.
BUFFER_LIKE_ADDR_WINDOWis doing real work — it rejects 67.12% inline, exactly as designed — but the surviving 32.88% each pay a function call, a thread-local resolution, aRefCellborrow and a hash lookup to discover, 99.999656% of the time, that the answer is no. The window spans 90.3 MB inside a 668.6 MB probed range, so a min/max bounding box simply cannot separate 9 addresses from the heap around them.The instrument already names the fix and sizes it: a 1024-bit, 3-hash Bloom filter over registered addresses would be 0.0% false-positive on this workload — 128 bytes of state replacing 26.2 M hash probes. It fits the same soundness contract as the window (every writer arms it before publishing; a negative is authoritative, a positive falls through to the existing set).
2. The deeper question: why 79.7 M probes for 9 buffers? Nothing in this workload is buffer-shaped — it is a type-check of two lines of TypeScript. At ~10 M probes/second something on a very hot path is asking "is this a registered buffer?" about values that are overwhelmingly not buffers and never could be.
is_registered_bufferhas ~200 call sites per its own comment; the probe volume suggests one of them sits in a generic value/type ladder that runs per property access or per value touched.Fixing (1) makes each probe ~free. Fixing (2) removes them. (2) is the larger win and is likely a small change once the caller is identified — a
PERRY_BUFFER_DIAGvariant that samples call-site backtraces would find it immediately.Why this is worth doing
is_registered_buffer_slowis 2.8% of the run on its own, but it is one member of a family: predicates answering "what kind of pointer is this?" are 18.5% of the post-fix profile —is_registered_box_ptris_registered_buffer_slowclassify_heap_generation_uncachedresolve_strategy_slowget_parent_class_idis_class_object_ptrkeys_find_slot_by_bytesis_registered_map,classify_arena, …This one is the most clear-cut instance because the ratio is so extreme (90 hits in 79.7 M probes) and the runtime already ships the instrument that proves it.
Reproduction
Related: the same probe family is discussed in #10688's context; GC census recorded 147 MB of side tables against a 23 MB live heap, which is the memory face of this design.