Repository navigation
perf: avoid quadratic removed-key scan and skip scalar auto-activation - #664
Open
deggertsen wants to merge 4 commits into
Open
deggertsen wants to merge 4 commits into
deggertsen wants to merge 4 commits into
Conversation
Two micro-optimizations to the observable node update path: 1. updateNodes: the removed-key scan called keys.includes(key) for every previous key, which is O(n*m) when many keys change at once. Add a same-position fast path and only build a Set of the new keys when both key lists exceed 32 entries. On a 3000-key rotation this cuts a set() from ~12ms to ~0.9ms (p50, M4 Pro). Small objects take the identical includes() path as before. 2. recursivelyAutoActivateInner: skip non-object/non-function values before the isObservable check. Scalars can never be observables or linked functions, so this avoids boxing every string during auto activation of large plain objects.
deggertsen
force-pushed
the
perf/updateNodes-set-and-activation-skip
branch
from
September 17, 2026 19:38
d5380a0 to
96e05af
Compare
deggertsen
force-pushed
the
perf/updateNodes-set-and-activation-skip
branch
from
September 17, 2026 19:42
96e05af to
4a5179a
Compare
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Reduce work when replacing observable objects and activating their contents while preserving child notifications and lazy activation.
includes().undefinedMap key.Set and
includes()membership both use SameValueZero semantics, preserving object-key identity,NaN, and signed-zero equivalence. Deletion handling retains its original order.Regression coverage
Object replacement and reordering at 32 and 33 keys, plus replacement of 2,000 keys: removed descendants, notification payloads and order, and retained listeners.
Map object-key identity,
NaN, signed zero, and removal of anundefinedkey after 0, 1, or 33 retained keys.Mixed-scalar activation with nested observables, arrays, automatic links, lazy links, and normal functions.
Replaced the single-sample 5 ms synced-activation assertion with a deterministic test in
tests/synced.test.ts. It verifies lazy get/subscribe calls, delivery of 10,000 records without child enumeration, and subsequent access to a nested value. Disabling the plain-data hint in a temporary checkout makes the test fail with 20,000 child enumerations, confirming that it catches eager traversal. The original timing assertion failed on CI at 5.64 ms.Stabilized the CRUD last-sync persistence test by awaiting the observable
lastSyncsignal, which is published after metadata is written, instead of assuming persistence finishes within 10 ms. All existing assertions remain.Validation
7c68964f: Test, Lint, and Format passed (run).npm test -- --runInBand --watchman=false).npm run typecheck,npm run build,npm run lint:check,npm run format:check, andgit diff --checkpassed.Local performance check
Measured September 10 on Apple M4 Pro, Node 26.3.1, using production-mode bundles of base commit
23b5ddeband the runtime changes in this PR. Object replacement alternates two objects after 50 warmup updates, with 50 measured samples. Per-update milliseconds for 3,000 keys:First root read of 3,000 string properties measured 0.165 ms → 0.090 ms median (50 warmups, 100 samples). Small-object cases at 16 and 33 keys remained in the low single-digit microseconds, with mixed small differences. These local measurements are not cross-engine guarantees or CI timing assertions.