Conversation
…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 <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 833cbdd The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Coverage Report for CI Build 34730721456Coverage remained the same at 71.842%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Merging this PR will degrade performance by 24.76%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | merge |
274.5 µs | 2,357.8 µs | -88.36% |
| ❌ | construct |
34.8 µs | 40.9 µs | -14.74% |
| ❌ | construct |
34.8 µs | 40.6 µs | -14.33% |
| ❌ | construct |
37.7 µs | 43.6 µs | -13.47% |
| ❌ | construct |
46.4 µs | 52.1 µs | -11% |
| ❌ | construct |
56.6 µs | 62.3 µs | -9.15% |
| ❌ | construct |
56.8 µs | 62.3 µs | -8.84% |
| ❌ | construct |
57 µs | 62.3 µs | -8.46% |
| ❌ | construct |
58.6 µs | 63.4 µs | -7.56% |
| ⚡ | merge |
109 µs | 86 µs | +26.69% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing perf/shared-prop-traps (833cbdd) with next (084e621)
|
Port of #3391 (
main,mergeProps/splitProps) to the 2.0 primitives in@solidjs/signals.What changes
Each
merge()/omit()proxy used to allocate a target holding three fresh closures (get/has/keys) over that instance's sources, with the sharedpropTrapsforwarding every trap call through them. Now the per-instance state lives on the target under symbol keys —{ [$SOURCES]: flattened }for merge,{ [$OMIT_PROPS]: props, [$OMIT_KEYS]: keys }for omit — and each primitive has one shared handler that reads it directly.Semantics are unchanged: same right-to-left
inlookup, samehas/ownKeysfiltering,$SOURCESstill answers the flat list on a merge proxy (whatmergeSources()/spread()read) andundefinedon an omit proxy (#3014). The state keys are never reported byownKeysnor answered byget/has, so they're invisible through the proxy; a stringObject.definePropertyon the proxy lands on the target without touching them (before, defining"get"would have overwritten the target's method and broken the proxy). Proxy invariants hold: the target is extensible and its state keys configurable.Tests
New test: state symbols not visible,
Object.defineProperty(merged, "sources", …)/(rest, "props", …)don't affect reads orin/keys, live reads after a store write, descriptor getters work. Existing #3014 test and the whole merge/omit suite unchanged. Suites: signals 1768, solid 595, html 200, web dom 735 / server 795 / hydrate 169.Performance
Measured on battery, so only directions are reliable (±20% run-to-run). Interleaved A/B, old vs new
dist/prodin one process, 31 alternating rounds:omit(merge(defaults, store), "id")batchmerge(mergeProxy, {x})Object.keys/{...proxy}inA computed-symbol literal costs ~13 ns vs ~46 ns for the three-closure literal in isolation, so the construct win is real but small next to the Proxy itself; the retained batch is where it shows. CodSpeed's
merge-proxy-keys/omit-proxyconstruct+read benches inutilities.bench.tscover this on the PR.