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

[Fabric] Optimize cloning ShadowTree when animating layout props #3369

Merged
merged 7 commits into from
Aug 16, 2022

Conversation

tomekzaw
Copy link
Member

@tomekzaw tomekzaw commented Jul 7, 2022

Description

This PR improves the implementation of ShadowTree cloning algorithm in NativeReanimatedModule::performOperations.

Previously, ShadowNode::cloneTree was called for each update, so effectively the root node was cloned $n$ times.

In this PR I've proposed an algorithm for solving this task more optimally. The main idea is that unsealed ShadowNodes can be still updated. In particular, their children can be replaced using ShadowNode::replaceChild. Also, freshly cloned ShadowNodes are unsealed, so each ShadowNode can be cloned only once and then updated.

One important thing to mention is that replaceChild does not update yogaChild_ field in ShadowNode, so we need to invoke ShadowNode::updateYogaChildren manually. This function has $O(n)$ time complexity where $n$ denotes the number of children. In order to avoid calling updateYogaChildren multiple number of times for the same ShadowNode, the calls are batched in std::set. Since in most use-cases there is no more than ~50 props updates, I've decided to use ordered set implementation with $O(\log{n})$ time complexity of insertion in order to save memory as well as avoid the overhead of calculating hash function (as well as to keep the order of updates).

Before After
Before After
Execution time measurements
nested view levels chessboard size old algorithm [μs] new algorithm [μs] speedup [x]
0 10x10 4008 1326 3,0
0 20x20 15962 3620 4,4
0 30x30 50287 5939 8,5
0 40x40 131410 10669 12,3
10 10x10 105792 4058 26,1
10 20x20 388404 5935 65,4
10 30x30 879948 9334 94,3
10 40x40 1598547 15477 103,3
50 10x10 524744 11750 44,7
50 20x20 2056805 17257 119,2
50 30x30 4657397 24588 189,4
50 40x40 8305191 35413 234,5

Changes

  • Implemented ShadowTreeCloner for optimal cloning ShadowTree when animating layout props on Fabric
  • Used ShadowTreeCloner in NativeReanimatedModule::performOperations
  • Moved creation of PropsParserContext to ShadowTreeCloner

Test code and steps to reproduce

The following examples can be used to test the algorithm:

  • WidthExample
  • RefExample
  • ChessboardExample
  • NewestShadowNodesRegistryRemoveExample

Checklist

  • Included code example that can be used to test this change
  • Updated TS types
  • Added TS types tests
  • Added unit / integration tests
  • Updated documentation
  • Ensured that CI passes

@tomekzaw tomekzaw changed the title Optimize cloning ShadowTree when animating layout props on Fabric [Fabric] Optimize cloning ShadowTree when animating layout props Jul 20, 2022
@piaskowyk piaskowyk mentioned this pull request Aug 13, 2022
@piaskowyk piaskowyk merged commit aae0fa1 into main Aug 16, 2022
@piaskowyk piaskowyk deleted the @tomekzaw/fabric-optimize-clone-shadow-tree branch August 16, 2022 08:43
fluiddot pushed a commit to wordpress-mobile/react-native-reanimated that referenced this pull request Jun 5, 2023
…tware-mansion#3369)

## Description

This PR improves the implementation of ShadowTree cloning algorithm in `NativeReanimatedModule::performOperations`.

Previously, `ShadowNode::cloneTree` was called for each update, so effectively the root node was cloned $n$ times.

In this PR I've proposed an algorithm for solving this task more optimally. The main idea is that unsealed ShadowNodes can be still updated. In particular, their children can be replaced using `ShadowNode::replaceChild`. Also, freshly cloned ShadowNodes are unsealed, so each ShadowNode can be cloned only once and then updated.

One important thing to mention is that `replaceChild` does not update `yogaChild_` field in ShadowNode, so we need to invoke `ShadowNode::updateYogaChildren` manually. This function has $O(n)$ time complexity where $n$ denotes the number of children. In order to avoid calling `updateYogaChildren` multiple number of times for the same ShadowNode, the calls are batched in `std::set`. Since in most use-cases there is no more than ~50 props updates, I've decided to use ordered set implementation with $O(\log{n})$ time complexity of insertion in order to save memory as well as avoid the overhead of calculating hash function.

| Before | After |
|:-:|:-:|
| <img width="180" alt="Before" src="https://user-images.githubusercontent.com/20516055/177736298-0ed230dc-edf2-40a5-9a1d-8400c9f58c39.png"> | <img width="159" alt="After" src="https://user-images.githubusercontent.com/20516055/177736518-fd9eeda6-025c-45f1-a636-b4a0665b0c23.png"> |

<details>
<summary><strong>Execution time measurements</strong></summary>

| nested view levels | chessboard size | old algorithm [μs] | new algorithm [μs] | speedup [x] |
|:-:|:-:|:-:|:-:|:-:|
| 0 | 10x10 | 4008 | 1326 | 3,0 |
| 0 | 20x20 | 15962 | 3620 | 4,4 |
| 0 | 30x30 | 50287 | 5939 | 8,5 |
| 0 | 40x40 | 131410 | 10669 | 12,3 |
| 10 | 10x10 | 105792 | 4058 | 26,1 |
| 10 | 20x20 | 388404 | 5935 | 65,4 |
| 10 | 30x30 | 879948 | 9334 | 94,3 |
| 10 | 40x40 | 1598547 | 15477 | 103,3 |
| 50 | 10x10 | 524744 | 11750 | 44,7 |
| 50 | 20x20 | 2056805 | 17257 | 119,2 |
| 50 | 30x30 | 4657397 | 24588 | 189,4 |
| 50 | 40x40 | 8305191 | 35413 | 234,5 |

</details>

<!--
Description and motivation for this PR.

Inlude Fixes #<number> if this is fixing some issue.

Fixes # .
-->

## Changes

- Implemented `ShadowTreeCloner` for optimal cloning ShadowTree when animating layout props on Fabric
- Used `ShadowTreeCloner` in `NativeReanimatedModule::performOperations`
- Moved creation of `PropsParserContext` to `ShadowTreeCloner`

<!--
Please describe things you've changed here, make a **high level** overview, if change is simple you can omit this section.

For example:

- Added `foo` method which add bouncing animation
- Updated `about.md` docs
- Added caching in CI builds

-->

<!--

## Screenshots / GIFs

Here you can add screenshots / GIFs documenting your change.

You can add before / after section if you're changing some behavior.

### Before

### After

-->

## Test code and steps to reproduce

<!--
Please include code that can be used to test this change and short description how this example should work.
This snippet should be as minimal as possible and ready to be pasted into editor (don't exclude exports or remove "not important" parts of reproduction example)
-->

The following examples can be used to test the algorithm:
- `WidthExample`
- `RefExample`
- `ChessboardExample`
- `NewestShadowNodesRegistryRemoveExample`

## Checklist

- [ ] Included code example that can be used to test this change
- [ ] Updated TS types
- [ ] Added TS types tests
- [ ] Added unit / integration tests
- [ ] Updated documentation
- [ ] Ensured that CI passes
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