Repository navigation
perf: read the type of a draft from its state when applying patches - #196
Merged
Merged
Conversation
|
Coverage after merging perf/apply-patch-paths into main will be
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
…eading symbol keys
… at 1,000 and 10,000 rows
|
Coverage after merging perf/apply-patch-paths into main will be
Coverage Report
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
This was referenced Oct 9, 2026
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.
Part of #168.
Applying patches that replace nested values was one of the few cases where Immer was faster: without auto-freeze,
apply-update-10pcttook 1.16–1.22 times as long as Immer at 100, 1,000 and 10,000 rows, 3 of the 6 cells that Immer won. A CPU profile of that scenario at 1,000 rows put 47% of the time ingetTypeand 6% more in thegetPrototypeOftrap:apply()asked every draft on a patch path for its type twice, once for the step itself and once inget()to read the next value, andgetTypetestsinstanceof Mapandinstanceof Set, each of which reaches thegetPrototypeOftrap of the draft's proxy. Immer reads its draft state once per step.apply()reads the type of a draft from its state. For a draft, the type comes from itsProxyDraft, one read through the proxy, and the type found for a step also reads the next value, instead ofget()finding it again.getType. A mutable application, which changes the objects of the application in place, usesgetTypealone and reads no symbol from them; in other applications, a value that rejects reading the symbol of drafts, such as a proxy of the application, is typed withgetTypetoo.size-limitcap of all ESM exports rises from 8.4 to 8.5 kB. Againstmain, in Brotli bytes: the production builds grow by 33 (CommonJS, 8,334 to 8,367), 35 (ESM) and 45 (UMD), the consumer bundles by 40–61, exceptcreatealone, which does not includeapply()and is unchanged, and the development builds by 62–81.size-limitmeasures 8.56, 7.50 and 8.41 kB against caps of 8.6, 7.5 and 8.5 kB.mainin a section of its own.Results
Mutative alone, the source of
mainat4ca6345and of this branch in alternating order, each scenario in processes of its own, three runs, Apple M1 Max, Node 24.16.0, µs per patch application:mainmainAll 14 patch application cells were faster, 0.68 of the time of
mainon geometric mean. With every library in processes of its own,apply-update-10pctis now faster than Immer in every cell, 1.6–2.0 times (10,000 rows without auto-freeze: 736 µs against 1,169 µs), and the published comparison becomes 551 / 12 / 3 of 566 against Immer (geometric mean 3.66, was 548 / 12 / 6 and 3.62) and 142 / 0 / 3 of 145 with each library's defaults (6.76, was 6.62); Immer is faster in 3 cases instead of 6. The README, the website and its home page now say about 3.7x and 6.8x instead of 3.6x and 6.6x.A mutable application of the same patches to plain objects takes 0.91–0.93 of the time of
mainat 100, 1,000 and 10,000 rows (Mutative alone, medians of three alternating processes per build).Correctness
apply()of this branch with that ofmain: 20,000 runs in production builds and 10,000 in development builds, on random states with objects, arrays, Maps, Sets and class instances, with patch lists that random recipes generated, with array and string paths, and with hand-written ones that are invalid or hostile (reserved keys and keys that convert to them, missing paths,replaceon a Set, unsupported operations, root replacement), in every mode: default, auto-freeze,mark,mutable, a draft as the state and strict mode. It compared the results, the errors and the untouched base: 0 differences. The two Set fuzzers, which replay patches throughapply(), ran 20,000 and 8,000 recipes: 0 differences.markthat keeps it mutable, as onmain; two new tests cover these cases.Not covered
markkeeps mutable or that cannot be drafted is still asked once per step for the symbol of drafts, as Immer'sapplyPatchesasks every value on a path for its own; a value that rejects the read is typed as before.createimport measures 7,495 of its 7,500 bytes, so the next change to it is likely to raise that cap.