-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Improve mutation processing performance #1652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 9a7a47e The changes in this PR will be included in the next version bump. This PR includes changesets to release 19 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 |
I've cloned the plunker from #1302 from @mdellanoce The microtasks run after the jstree work is done, and I'm clocking 3.2s for those before this PR vs. 2.2s after. |
Here's the benchmark output (please replicate on other machines):
|
…acktrack; pushAdd requires that each new node has a parentId and a nextId
…er of child nodes share the same parent
…hat if a possibly null node is in e.g. this.addedSet, then it is indeed not null. Similarly if `this.addedSet.size` is non zero, then we can pop confidently
… confusing initialization to `IGNORED_NODE`
… equivalent but might have implications in terms of how replay should iterate over them
…checks if it goes bad early
… to mutation processing
…was preventing benchmark test from running
… could also use `checkLoops: allExceptWhileTrue` after eslint upgrade
… could be applied across all snapshots. Also, only mutation events are relevant here (reduce burden when manually expecting test output). Also (commented out) showing how to reuse expected output between tests, i.e. assert that two tests produce same output
…esent on mutation processing before they could be added, but couldn't (deliberately) break anything. Adding test anyway as it might throw up an interesting scenario
…a which could be applied across all snapshots. Also, only mutation events are relevant here (reduce burden when manually expecting test output). Also (commented out) showing how to reuse expected output between tests, i.e. assert that two tests produce same output
…g and a speculative mitigation
… in the DOM/mirror prior to mutation processing
… already be in the correct order for a single pass
7fde788
to
9a7a47e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ugh, my youngest is awake...
I promised i'd do a review but i've not looked at mutation code much before so this first pass is much more to check my understanding than anything else
i have a customer site that i know is causing us problems with missing nodes at playback, will try and figure out if there's a way i can test this code on that site to compare the output (i don't think i can without customer changing their code)
that site is a stock ticker so maybe i can make a minimal example myself quickly
(maybe it goes without saying but feel free to tell me where i'm being a fool, i'm keen to understand this all more)
@@ -1506,7 +1506,7 @@ export class Replayer { | |||
// is newly added document, maybe the document node of an iframe | |||
return this.newDocumentQueue.push(mutation); | |||
} | |||
return queue.push(mutation); | |||
return legacy_queue.push(mutation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fly by that the other item marked legacy here only made me wonder why it was legacy
so there's a signal that maybe it's not current
but I don't know what the consequences are
e.g. is the code only going to run on data recorded prior to version X
i don't know where the best place for that info is
but i worry it's in someone's head and could be lost :)
while (true) { | ||
parentNode = dom.parentNode(n); | ||
if (this.addedSet.has(parentNode as Node)) { | ||
// start at top of added tree so as not to serialize children before their parents (parentId requirement) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// start at top of added tree so as not to serialize children before their parents (parentId requirement) | |
// keep searching for the top of added tree so as not to serialize children before their parents (parentId requirement) |
nit... "start at the top" threw me here and made me think 🙈
} | ||
} | ||
|
||
if (this.addedSet.has(parentNode.lastChild as Node)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess the casts to Node are to avoid !!parentNode.lastChild && x.has(parentNode.lastChild)
i think safe here but as X
is such a source of bugs that it always scares me 🙈
Refactor record time mutation processing to improve performance
this.addedSet
so that we start with nodes which already have the requirednextId
andparentId
This solves pathological cases where nodes from the addedSet were pushed onto the secondary addList, possibly multiple times as
pushAdd
was called again each time the nextId/parentId requirements weren't met.Previous efforts in this direction were
Also related is #1277 "refactor recursive procedure to iterative" which we could also incorporate on top of this as it should provide orthogonal performance gains — in order to 'grasp the nettle' all at once in terms of possible breakage.