Skip to content
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

Perf: optimize the performance of processMutations #1187

Closed

Conversation

wfk007
Copy link
Contributor

@wfk007 wfk007 commented Mar 23, 2023

processMutations contains two phases:

  1. processMutation
  2. emit

and they have bad performance in some cases.

processMutation

case

In the case below: (reproduce link: https://github.com/wfk007/test-processMutation)

we create 1000 ElementNode and insert them into #target, each Node is the parent of the following Node

<html>
    <body>
        <button id="add-nodes">Add multi nodes</button>

        <div id="target"></div>

        <script src="https://cdn.jsdelivr.net/npm/rrweb@latest/dist/record/rrweb-record.min.js"></script>
        <script>
            const addNodes = document.querySelector('#add-nodes');
            const target = document.querySelector('#target');

            addNodes.addEventListener('click', () => {
                const total = 1000;
                let parent = target;
                for (let i = 0; i < total; i++) {
                    const el = document.createElement('div');
                    el.textContent = `el-${i + 1}`;
                    parent.append(el);
                    parent = el;
                }
            });
            rrwebRecord({
                sampling: {
                    mouseInteraction: false,
                    mousemove: false,
                    scroll: 300,
                    input: 'last',
                },
                slimDOMOptions: true,
                emit(event) {
                    console.log(event);
                },
            });
        </script>
    </body>
</html>

performance

WITHOUT record, it takes about 50ms to render

image


WITH record it takes 655ms and processMutation takes 604ms

image

reason

The dom structure below can be created in many ways:

body
    E1
       E2

Approach 1:

  1. create E1
  2. create E2
  3. E1 append E2
  4. body append E1

Browser Mutation observer callback result:

[
    {
        target: body,
        addedNodes: [E1],
        type: "childList",
    },
]

approach 2:

  1. create E1
  2. body append E1
  3. create E2
  4. E1 append E2

Browser Mutation observer callback result:

[
    {
        target: body,
        addedNodes: [E1],
        type: "childList",
    },
    {
        target: E1,
        addedNodes: [E2],
        type: "childList",
    },
]

In order to cover different Mutation observer callback results and track adds/removes Nodes, RRweb loops all mutations and its' addedNodes.
Each Node in addedNodes will still travel all its' childNodes.

Considering approach2 result:

  1. loop array index 0:
    travel E1
    travel E2(which is the childNode of E1)
  2. loop array index 1:
    travel E2(no need to travel E2 and its' childNodes again)

solution

In genAdds, if the node is added to addedSet or movedSet, there is no need to travel it and its' children again

after optimizing, it takes 160ms and processMutation takes 77ms

image

emit

case

(case is on the way)

performance

when the size of addedSet is huge(20k+ Nodes), the for-loop is very slow with time complexity O(n^2) to find a valid Node

image

reason

image
There are 20000 nodes in addList, and index 900 is the first valid Node from right to left, but its' previous is not a valid Node. we loop from right to left to find the first valid Node

  1. addList.get(19999): O(N) time complexity and it is very slow
  2. addList.get(19998): O(N) time complexity and it is slow too
  3. ...
  4. addList(900): find valid Node
  5. 899 is selected as candidate Node
  6. 899 is invalid
  7. so we loop from right to left
  8. repeat 1/2/3...

It is very very slow(takes about 16s).

solution

I create a linkedNodeList based on DoubleLinkedList to accelerate node query with array index, and query time complexity decrease from O(n) to O(1) with a huge performance boost

image

image

@changeset-bot
Copy link

changeset-bot bot commented Mar 23, 2023

🦋 Changeset detected

Latest commit: 3557938

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

This PR includes changesets to release 7 packages
Name Type
rrweb Patch
rrweb-snapshot Patch
rrdom Patch
rrdom-nodejs Patch
rrweb-player Patch
@rrweb/types Patch
@rrweb/web-extension 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

@wfk007 wfk007 force-pushed the optimization-of-processMutation branch from 76aa2a7 to 7960f18 Compare March 24, 2023 02:07
@wfk007 wfk007 force-pushed the optimization-of-processMutation branch from df1d889 to 3557938 Compare March 27, 2023 05:40
@wfk007 wfk007 changed the title optimize processMutation Perf: optimize the performance of processMutations Mar 27, 2023
@Juice10
Copy link
Contributor

Juice10 commented Apr 7, 2023

Hey @wfk007 this is a great performance improvement, thank you so much for putting all the hard work into this.
One thing I'm missing is making sure your hard work doesn't get reverted by accident in the future.
We have a benchmark suite in the test suite that would be a great candidate for this, especially in combination with a GitHub action (that we don't have yet) https://github.com/benchmark-action/github-action-benchmark that'll make sure performance gets tested on each pull request.

@eoghanmurray
Copy link
Contributor

I haven't looked too closely but the change is nice and small and seems to make sense.
I'm putting this out to a 'canary' environment today to see if any problems arise.
Note: The ESLint failure can be solved by rebasing upon the current trunk.

@wfk007
Copy link
Contributor Author

wfk007 commented Apr 20, 2023

Hey @wfk007 this is a great performance improvement, thank you so much for putting all the hard work into this. One thing I'm missing is making sure your hard work doesn't get reverted by accident in the future. We have a benchmark suite in the test suite that would be a great candidate for this, especially in combination with a GitHub action (that we don't have yet) https://github.com/benchmark-action/github-action-benchmark that'll make sure performance gets tested on each pull request.

@Juice10 @eoghanmurray I have been trapped at work and complex trivial things in life recently, and I will add a benchmark test and fix the Lint error later, thanks for your review and great suggestions.

@YunFeng0817 YunFeng0817 self-requested a review April 27, 2023 13:26
@wfk007
Copy link
Contributor Author

wfk007 commented Apr 28, 2023

@Juice10 @eoghanmurray Hi, I plan to separate this PR into two, thanks for your great suggestions again

part one: #1214

part two: #1220

@wfk007 wfk007 closed this Apr 28, 2023
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.

4 participants