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

improve interpolation performance with big input range #33598

Closed
wants to merge 3 commits into from
Closed

improve interpolation performance with big input range #33598

wants to merge 3 commits into from

Conversation

Almouro
Copy link
Contributor

@Almouro Almouro commented Apr 8, 2022

Summary

This drastically improves Animated.interpolate performance when inputRange has a considerable amount of elements (~100 in my tests).

For instance in ActivityIndicator inside react-native-paper, the input has 144 elements https://github.com/callstack/react-native-paper/blob/main/src/components/ActivityIndicator.tsx#L170. react-native-elements has 9k stars, so I'm assuming this is widely used.

Cause

The reason for the performance drop is that if we assume n to be the size of the range, calculating 'inputRange must be monotonically non-decreasing ' + arr essentially calculates arr.toString() which has O(n) complexity.
Since it is recalculated in a for loop, we end up with checkValidInputRange having a O(n²) complexity. Which means ~10k operations if the array has a size close to 100.

Changelog

[General] [Fixed] - Fix performance issue on Animated.interpolate with big input range

Test Plan

Here's a repo reproducing the issue.
The branch fix includes the fix.
Clicking Interpolate runs:

new Animated.Value(0).interpolate({
  inputRange: Array(144)
    .fill()
    .map((_, i) => 1 / (i + 1))
    .reverse(),
  outputRange: Array(144)
    .fill()
    .map((_, i) => 1 / (i + 1))

Here's a comparison of JS thread perf before the fix and after the fix:

Before After
image image

The error still throws if inputRange is incorrect:

image

However if __DEV__ mode is deactivated, no error is thrown

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Apr 8, 2022
@javache
Copy link
Member

javache commented Apr 8, 2022

Thanks for spotting this! I think all of the sanity checks in this file should probably be disabled in release builds. That will also enable dead code stripping to remove these checks.

Can you add an if (__DEV__) { check around the calls to checkInfiniteRange / checkValidInputRange and other invariants inside createInterpolation instead?

@javache javache self-requested a review April 8, 2022 10:11
@javache javache self-assigned this Apr 8, 2022
@javache javache removed their request for review April 8, 2022 10:11
@analysis-bot
Copy link

analysis-bot commented Apr 8, 2022

Platform Engine Arch Size (bytes) Diff
ios - universal n/a --

Base commit: bfc3935
Branch: main

@Almouro
Copy link
Contributor Author

Almouro commented Apr 8, 2022

Hi @javache, thanks!

It feels to me like this could warrant a bigger discussion on invariant in release in general. What do you think?

  • One pro of having them in release is they pop up on crash reporting tools such as Sentry, and can useful to analyze bugs.
  • On the other hand, keeping them can be bad for performance, so if we go this route here, I'd be happy to codemod it and surround every invariant in RN by if (__DEV__)

@analysis-bot
Copy link

analysis-bot commented Apr 8, 2022

Platform Engine Arch Size (bytes) Diff
android hermes arm64-v8a 7,767,516 -562
android hermes armeabi-v7a 7,171,437 -577
android hermes x86 8,073,746 -581
android hermes x86_64 8,054,066 -570
android jsc arm64-v8a 9,645,100 -179
android jsc armeabi-v7a 8,419,461 -183
android jsc x86 9,594,319 -175
android jsc x86_64 10,191,730 -191

Base commit: bfc3935
Branch: main

@javache
Copy link
Member

javache commented Apr 8, 2022

Some invariants are important to keep around in production, because there may be states we really don't want the application to get into and do want to see escalated to crash reporting.

Other invariants, like these ones, in my opinion, are more about catching developer errors, and can be skipped in release. The trade-off here is that we may miss some errors if the inputs to the interpolate call are not static, but I think that's reasonable here.

@Almouro
Copy link
Contributor Author

Almouro commented Apr 8, 2022

Cool, thanks for the explanation! 🙏
Added the __DEV__ wrapping as well then

* doesn't cleanly convert to a string, like undefined, null, and object,
* etc. If you really mean this implicit string conversion, you can do
* something like String(myThing) */
const message = 'inputRange must be monotonically non-decreasing ' + arr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this + String(arr) and remove the FlowFixMe statement?

@facebook-github-bot
Copy link
Contributor

@javache has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@react-native-bot
Copy link
Collaborator

This pull request was successfully merged by @Almouro in f503b21.

When will my fix make it into a release? | Upcoming Releases

@react-native-bot react-native-bot added the Merged This PR has been merged. label Apr 11, 2022
Saadnajmi pushed a commit to Saadnajmi/react-native-macos that referenced this pull request Jan 15, 2023
Summary:
This drastically improves `Animated.interpolate` performance when `inputRange` has a considerable amount of elements (~100 in my tests).

For instance in `ActivityIndicator` inside `react-native-paper`, the input has 144 elements https://github.com/callstack/react-native-paper/blob/main/src/components/ActivityIndicator.tsx#L170. `react-native-elements` has 9k stars, so I'm assuming this is widely used.

### Cause

The reason for the performance drop is that if we assume `n` to be the size of the range, calculating `'inputRange must be monotonically non-decreasing ' + arr` essentially calculates `arr.toString()` which has O(n) complexity.
Since it is recalculated in a for loop, we end up with `checkValidInputRange` having a O(n²) complexity. Which means ~10k operations if the array has a size close to 100.

## Changelog

[General] [Fixed] - Fix performance issue on Animated.interpolate with big input range

Pull Request resolved: facebook#33598

Test Plan:
[Here's a repo](https://github.com/Almouro/AnimatedInterpolationRepro) reproducing the issue.
The branch `fix` includes the fix.
Clicking `Interpolate` runs:

```js
new Animated.Value(0).interpolate({
  inputRange: Array(144)
    .fill()
    .map((_, i) => 1 / (i + 1))
    .reverse(),
  outputRange: Array(144)
    .fill()
    .map((_, i) => 1 / (i + 1))
```

Here's a comparison of JS thread perf before the fix and after the fix:
- on a Samsung J3 2017 (lower end)
- using Flipper and https://github.com/bamlab/react-native-performance)
- ` __DEV__` mode deactivated
- clicking the button and waiting 15s

| Before   |      After      |
|----------|:-------------:|
| ![image](https://user-images.githubusercontent.com/4534323/162413692-307c2be1-5c7f-4e7f-ba69-8ba8d7c52bda.png) |  ![image](https://user-images.githubusercontent.com/4534323/162413842-780f12d2-ce8b-457c-b66c-c6d86f14ed28.png)|

The error still throws if `inputRange` is incorrect:

<img width="517" alt="image" src="https://user-images.githubusercontent.com/4534323/162439219-6ce120ae-98e5-496b-899a-492978689d6d.png">

However if `__DEV__` mode is deactivated, no error is thrown

Reviewed By: yungsters

Differential Revision: D35507441

Pulled By: javache

fbshipit-source-id: 36ac49422f7a42d247130c42d12248b2be1232c6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants