Repository navigation
perf: reduce props proxy closure allocations - #3391
DaniGuardiola wants to merge 2 commits into
Conversation
> 🤖🔧 ai generated
> 🤖🔧 ai generated
|
…target Each merge()/omit() proxy allocated a target holding three fresh closures (get/has/keys) over the instance's sources and forwarded every trap call through them. Keep the sources/keys on the target under symbol keys and share one handler per primitive instead: a props proxy is a Proxy plus a one- or two-slot object, and a read goes straight from the trap to the sources. The state keys are never reported by ownKeys nor answered by get/has; a string defineProperty on the proxy lands on the target without touching them (previously it could overwrite the target's `get` method). Behaviour is unchanged: `$SOURCES` still answers the flat list on a merge proxy (mergeSources/spread) and undefined on an omit proxy (#3014); the has/ownKeys filtering is the same. Port of #3391 (main) to next. Interleaved A/B, old vs new dist in one process, on battery (±20% noise): retained 200-instance merge+omit batch 0.87–0.93×, nested merge over a merge proxy 0.65–0.75×, Object.keys / spread 0.90–0.98×; single construct and reads within noise in process-isolated alternating runs. Co-authored-by: Cursor <[email protected]>
|
Thanks for this. We're closing it because Solid 1.x is close to end of life, and we're keeping its patch releases to bug fixes. Generated by Claude Code |
Summary
Replace per-instance proxy target closures in
mergePropsandsplitPropswith private-symbol data read by shared handlers. Proxy counts and public types stay unchanged. Local benchmarks showed 19–46% lower median creation time across four runs.What changes?
The proxy handlers are already shared today. However, each output proxy's target contains three newly created methods (
get,has,keys) closing over that instance's data. This change stores the data on the target and moves that work into shared functions.Simplified target construction:
For 10,000 merge instances, both versions allocate 10,000 proxies and targets, but the new version avoids 30,000 instance-specific methods. Reads also skip the forwarding call from handler to target method.
splitPropsuses the same approach for source, selected keys, and remainder state.Private symbols prevent
Object.definePropertyon names such assourceorsourcesfrom overwriting internal state. The regression test covers that isolation, symbol visibility, and live reads after store updates.Performance
Local comparison on Apple M4 Pro, Node 25.9.0 / V8 14.1, Vitest 4.1.10 with jsdom. Four fresh processes, alternating implementation order. Each operation handles 10,000 retained instances backed by distinct real stores; source setup is outside timing. Reads access four fields per instance.
Times are milliseconds per batch, reported as the median of four per-run means:
Creation improved in every paired run, but reductions varied substantially: 14–40% for merge, 13–54% for split, and 13–56% for the chain. Read results were less consistent, including one noisy merge-read result (8.36 ms, ±23% RME) that did not recur. These are exploratory local measurements, not browser-wide performance guarantees; allocation, GC, and engine optimization effects have not been separated.
The PR includes the six retained-instance benchmarks for reproducing the workload across revisions; the local comparison harness and saved results are not included.
How did you test this change?
pnpm run buildpnpm test— 492 runtime tests passed, plus type, Babel preset, and import checks.pnpm --dir packages/solid exec vitest bench test/props-retained.bench.ts --run— all 6 cases passed.