Skip to content

perf(store): stamp owned backings with $OWNER instead of weak-collection registrations - #3368

Merged
ryansolid merged 1 commit into
nextfrom
perf/owned-raw-stamp
Sep 11, 2026
Merged

perf(store): stamp owned backings with $OWNER instead of weak-collection registrations#3368
ryansolid merged 1 commit into
nextfrom
perf/owned-raw-stamp

Conversation

@ryansolid

@ryansolid ryansolid commented Sep 11, 2026

Copy link
Copy Markdown
Member

Part two of #3360 (part one: #3367, merged as 0f14430).

Fixes #3360

Problem

After #3367, the remaining floor of a one-key store write was two weak-collection registrations per draft on a fresh objectownedRaw.add(pb) and (fam.map ?? storeNextLookup).set(pb, target). V8 has to assign an identity hash and register ephemerons for each; measured 240–330 ns per pair, i.e. most of the 340 ns write.

Change

Every backing the store allocates (CoW clones, privatized committed backings) carries its owning target under an internal enumerable symbol, $OWNER:

  • cloneRaw stamps its output (spread and descriptor paths); overlay drafts (Object.create(v) over an owned v) inherit the stamp. ensurePB, privatizeCommitted, materializePB, flattenOverlay, drainFolds no longer touch any weak collection.
  • storeNextLookup / family maps now hold only unowned raws (user-ingested, adopted). lookupTarget(raw, fam) resolves stamp-first when the owner is in that family, else the map — every former (fam?.map ?? storeNextLookup).get site uses it. targetsEqual uses lookupTarget(_, null), so plain-store vs family semantics are exactly as before.
  • adoptPB re-stamps a same-family owned adoptee (the stamp is its registration) and maps everything else; reconcile's entity swap disowns the outgoing owned raw so a re-handed raw wraps fresh (proj R10 parity).
  • Enumerable on purpose: a spread copy stays on V8's fast clone path (a non-enumerable defineProperty stamp bails it, ~3× slower) and carries the stamp along.

Because owned raws are never user-reachable (snapshot copies them, unwrap is gone in 2.0), the stamp only needs to be hidden inside the store:

  • Traps: get/has/getOwnPropertyDescriptor answer undefined/false for $OWNER; ownKeys (visibleKeys) strips it.
  • Key walks skip it: membershipChanged (count minus stamp), deep-witness diff, dev onStoreNodeUpdate diff, reportReplacedContainers, snapshot copy loops, reconcile symbol pass, optimistic stagedApply / notifyOptimisticWrites / applyTentative, and the legacy affects-scope walk (which now carries the family instead of a map).

Numbers (prod dist)

rc.0 rc.7 #3367 this PR
steady one-key write incl. flush (1000 stores × 1000) 214 ns 674 ns 340 ns ~178 ns
reporter shape, 100k stores first write + flush 230 ms ~90 ms 51 ms

Existing store benches (dev mode, vs #3367): dbmon full/partial tick +30–50%, reconcile tree reverse +84% / shuffle +17%, selection map +33%, overlay delete+set root key +42%, commit boundary +29%, projection root/nested +27% / +8%. Nothing regressed locally.

Tests

tests/store/owner-stamp.test.ts (9): stamp on the committed backing / user object untouched; invisible through every trap incl. spreads (user symbols still enumerate); never in snapshot; never acquires a node or has-node; no spurious membership change on first commit onto an unowned backing; no spurious deep-witness bump on non-plain records or arrays; same-family alias resolves to the same proxy; a store's owned raw handed to a projection stays owned across families (snapshot copies). The membership and deep-witness tests were verified to fail with their guards removed.

Gates: signals dev suite green, prod-tier parity (238 baseline), solid 595, web 734, gc test baseline. Size gate: createStore 14.97 → 15.06 KB cap (measured 15.012), hydrating + stores 27.57 → 27.66 KB (27.608), notes in .size-limit.js.

Note on #3367's CodSpeed flag

CodSpeed flagged createStore setter: delete + set one root key (#3044 overlay) on part one (155.7 → 440.8 µs) with a "different runtime environments" warning. Locally that bench goes next 268k → #3367 464k → this PR 910k ops/s; I could not reproduce a regression with the JIT on and can't run instruction-count simulation here.

Claude via Cursor

Made with Cursor

@changeset-bot

changeset-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5aee76d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@solidjs/signals Patch
test-integration Patch
@solidjs/web Patch
@solidjs/babel-plugin Patch
@solidjs/compiler Patch
@solidjs/diagnostics Patch
@solidjs/element Patch
@solidjs/h Patch
@solidjs/html Patch
solid-js Patch
@solidjs/universal Patch

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

…ion registrations (#3360)

Every backing the store allocates (CoW clones, privatized committed
backings) now carries its owning target under an enumerable symbol. This
replaces the per-draft ownedRaw.add + raw->target map.set that dominated
the one-key write floor (V8 identity hash + ephemeron registration on a
fresh object). storeNextLookup / family maps now hold only unowned raws;
lookupTarget resolves stamp-first within a family.

The stamp is hidden by the get/has/getOwnPropertyDescriptor/ownKeys traps,
skipped by snapshot, membership and deep-witness diffs, reconcile,
optimistic staging and affects walks, and never acquires a node.

Steady-state single-key writes: 340 -> ~178 ns (rc.7 674, rc.0 214).

Fixes #3360

Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@ryansolid
ryansolid changed the base branch from perf/store-write-floor to next September 11, 2026 07:33
@ryansolid ryansolid closed this Sep 11, 2026
@ryansolid ryansolid reopened this Sep 11, 2026
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 34575057520

Coverage remained the same at 71.842%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1007
Covered Lines: 772
Line Coverage: 76.66%
Relevant Branches: 790
Covered Branches: 519
Branch Coverage: 65.7%
Branches in Coverage %: Yes
Coverage Strength: 15.01 hits per line

💛 - Coveralls

@codspeed-hq

codspeed-hq Bot commented Sep 11, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 22.18%

⚡ 4 improved benchmarks
✅ 156 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
steady state: owned backings (#3360) 11.1 ms 7.5 ms +47.32%
fresh stores: create + first write + first commit (reporter shape) 22.3 ms 17.3 ms +28.96%
commit boundary: flush after every 2-key setter, no subscribers (#3044) 10.4 ms 9.5 ms +8.98%
input burst: 200 single-key writes, 1 subscriber 4.9 ms 4.6 ms +7.62%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing perf/owned-raw-stamp (5aee76d) with next (0f14430)

Open in CodSpeed

@ryansolid
ryansolid merged commit 4935c7d into next Sep 11, 2026
7 checks passed
@ryansolid
ryansolid deleted the perf/owned-raw-stamp branch September 11, 2026 08:00
ryansolid added a commit that referenced this pull request Sep 11, 2026
`next`'s #3367/#3368 (narrow-store write floor, `$OWNER` stamp) under
the A28 seams: createStore 15.15 -> 15.42 KB (15384 B), hydrating +
stores 27.90 -> 28.15 KB (28112 B), hydrating 18.48 -> 18.52 KB (18484
B, layout). Core floor unchanged at 22,381.

Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
ryansolid added a commit that referenced this pull request Sep 11, 2026
`next`'s #3367/#3368 store bytes (via #3337) under the store twins:
createStore 15.45 -> 15.72 KB (15678 B), hydrating + stores 28.50 ->
28.66 KB (28620 B). Core floor unchanged at 22,866.

Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
ryansolid added a commit that referenced this pull request Sep 11, 2026
`next`'s #3367/#3368 (narrow-store write floor, `$OWNER` stamp) under
the A28 seams: createStore 15.15 -> 15.42 KB (15384 B), hydrating +
stores 27.90 -> 28.15 KB (28112 B), hydrating 18.48 -> 18.52 KB (18484
B, layout). Core floor unchanged at 22,381.

Co-authored-by: Claude via Cursor <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants