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

Performance optimization #1879

Merged
merged 31 commits into from
Jun 2, 2021
Merged

Performance optimization #1879

merged 31 commits into from
Jun 2, 2021

Conversation

piaskowyk
Copy link
Member

@piaskowyk piaskowyk commented Mar 30, 2021

Benchmarks

bezier - before 11 FPS, after 52 FPS
import Animated, {
  useSharedValue,
  withTiming,
  useAnimatedStyle,
  Easing
} from 'react-native-reanimated';
import { View, Button } from 'react-native';
import React from 'react';

function AnimatedStyleUpdateExample(): React.ReactElement {
  const randomWidth = useSharedValue(10);

  const config = {
    duration: 500,
    easing: Easing.bezier(0.5, 0.01, 0, 1),
  };

  const itemCount = 100;

  const components = [];
  for(let i = 0; i < itemCount; i++) {
    components.push(
      <Animated.View key={i}
        style={[
          { width: 100, height: 5, backgroundColor: 'black', margin: 1 },
          useAnimatedStyle(() => {
            return {
              width: withTiming(randomWidth.value, config),
            };
          }),
        ]}
      />
    )
  }

  return (
    <View
      style={{
        flex: 1,
        flexDirection: 'column',
      }}>
      <Button
        title="toggle"
        onPress={() => { randomWidth.value = Math.random() * 350; }}
      />
      { components }
    </View>
  );
}
export default AnimatedStyleUpdateExample;
withTiming rotate - before 35 FPS, after 60 FPS
import Animated, {
  useSharedValue,
  withTiming,
  useAnimatedStyle,
  withRepeat,
} from 'react-native-reanimated';
import { View, Button } from 'react-native';
import React from 'react';

const dummy = new Array(100).fill(1);
function AnimatedStyleUpdateExample() {
  return (
    <View style={{ flex: 1, flexDirection: "row", flexWrap: "wrap" }}>
      {dummy.map((_, i) => {

        const val = useSharedValue(0);
        const DEG = Math.PI * 2 * 10;
        val.value = withRepeat(
          withTiming(DEG, { duration: 5000 }),
          -1, true
        );

        const style = useAnimatedStyle(() => {
          'worklet'
          return {
            transform: [
               { rotate: val.value + 'deg' },
            ],
          };
        });

        return (
          <Animated.View
            key={i}
            style={[
              { width: 50, height: 50, borderWidth: 1 },
              style,
            ]}
          />
        );
      })}
    </View>
  );
}

export default AnimatedStyleUpdateExample;
colors interpolation - before 11 FPS, after 16 FPS
import Animated, {
  useSharedValue,
  withTiming,
  useAnimatedStyle,
  withRepeat,
  interpolateColor,
} from 'react-native-reanimated';
import { View } from 'react-native';
import React from 'react';

const dummy = new Array(100).fill(1);
function AnimatedStyleUpdateExample() {
  return (
    <View style={{ flex: 1, flexDirection: "row", flexWrap: "wrap" }}>
      {dummy.map((_, i) => {

        const color = useSharedValue(0);
        color.value = withRepeat(
          withTiming(1, { duration: 5000 }),
          -1, true
        );

        const style = useAnimatedStyle(() => {
          'worklet'
          return {
            backgroundColor: interpolateColor(color.value, [0, 1], ['#ffffff', '#000000'])
          };
        });

        return (
          <Animated.View
            key={i}
            style={[
              { width: 50, height: 50, borderWidth: 1 },
              style,
            ]}
          />
        );
      })}
    </View>
  );
}

export default AnimatedStyleUpdateExample;

I tested it on a regular iOS simulator so the optimization results are just indicative.

Checklist

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

@piaskowyk piaskowyk marked this pull request as draft March 30, 2021 17:06
@mrousavy
Copy link
Contributor

@piaskowyk everytime we run RuntimeDecorator::isXRuntime() we do a property getter, do you think we can optimize that in some way? It would be awesome if we could store a C++ property (enum?) in the jsi::Runtime instance that represents it's type. (Worklet & UI, just Worklet, React-JS)

@mrousavy
Copy link
Contributor

mrousavy commented Apr 11, 2021

What's styleUpdater()? Is that the valueSetter in the RuntimeManager? Couldn't that be written in C++ instead of JS?

@mrousavy
Copy link
Contributor

Also, #1863 seems to be somewhat related

@piaskowyk
Copy link
Member Author

@piaskowyk everytime we run RuntimeDecorator::isXRuntime() we do a property getter, do you think we can optimize that in some way? It would be awesome if we could store a C++ property (enum?) in the jsi::Runtime instance that represents it's type. (Worklet & UI, just Worklet, React-JS)

Good point 👍, I did it here: 48e1a2a

What's styleUpdater()? Is that the valueSetter in the RuntimeManager? Couldn't that be written in C++ instead of JS?

The styleUpdater() is JS function calling by mapper: https://github.com/software-mansion/react-native-reanimated/blob/master/src/reanimated2/Hooks.ts#L225
But I speed up this method. 🚀

piaskowyk and others added 6 commits May 6, 2021 12:59
Uses `unordered_map` registration for faster runtime checks (worklet, UI).

Everything is managed by the `RuntimeDecorator`, it's faster because it only compares pointers using an `unordered_map`, and it also works for every worklet runtime (VisionCamera, Multithreading). Also it's less code and the backwards compatible 🤗
@@ -711,7 +714,13 @@ const getInterpolateCacheRGBA = (
}
}
const newCache = { r, g, b, a };
const overrideHash = hashOrderRGBA[curentHashIndexRGBA];
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please use LRU Cache here? https://stackoverflow.com/a/46432113

@piaskowyk piaskowyk merged commit 5d2cf0f into master Jun 2, 2021
@piaskowyk piaskowyk deleted the @piaskowyk/performance-research branch June 2, 2021 12:49
@NikitaDudin
Copy link
Contributor

NikitaDudin commented Jun 2, 2021

App crashes on using style transform prop.

Sample code:

import React, { useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import Animated, {
  Easing,
  interpolate,
  useAnimatedStyle,
  useSharedValue,
  withRepeat,
  withTiming,
} from 'react-native-reanimated';

const styles = StyleSheet.create({
  main: {
    flex: 1,
    backgroundColor: '#accc00',
    justifyContent: 'center',
    alignItems: 'center',
  },
  box: {
    width: 100,
    height: 100,
    backgroundColor: '#a00',
  },
});

function Comp(): JSX.Element {
  const progress = useSharedValue(0);

  const style = useAnimatedStyle(() => ({
    width: interpolate(progress.value, [0, 0.5, 1], [100, 200, 100]),
    transform: [{ rotate: `${interpolate(progress.value, [0, 1], [0, Math.PI * 2])}rad` }],
  }), [progress]);

  useEffect(() => {
    progress.value = withRepeat(withTiming(1, { duration: 2000, easing: Easing.linear }), -1, false);
  }, [progress]);

  return (
    <View style={styles.main}>
      <Animated.View style={[styles.box, style]} />
    </View>
  );
}

export default Comp;

Error message:

node_modules/react-native-reanimated/src/reanimated2/Hooks.ts (162:0)

Possible solutions are:
a) If you want to synchronously execute this method, mark it as a Worklet
b) If you want to execute this method on the JS thread, wrap it using runOnJS

If comment out the code in isAnimated, then error disappears.

function isAnimated(prop) {
  'worklet';
  if (Array.isArray(prop)) {
    // comment problem section - START
    // for (const item of prop) {
    //   for (const key in item) {
    //     if (item[key].onFrame !== undefined) {
    //       return true;
    //     }
    //   }
    // }
    // comment problem section - END
    return false;
  }
  return prop.onFrame !== undefined;
}

Package versions

React Native: 0.64.1
NodeJS: 14.17.0
Hermes engine: enabled

Upd: after replacing all for..of on forEach problem disappears. What another way to fix it?

piaskowyk pushed a commit that referenced this pull request Mar 4, 2022
## Description

After #1879 was implemented (and also #2792), the mocks weren't updated. This PR fixes that.

The error is the same as in #2766, but only shows up when running tests.

## Changes

The mocks now match the signature of the functions.

## Test code and steps to reproduce

There wasn't anywhere obvious to add a test for this - as far as I can see, we don't have any tests that use the mocks. I'm happy to add one though if you point me in the right direction!

To reproduce, it's as simple as a jest (with mocks set up) test that includes code like:

```typescript
const someEasing = Easing.bezierFn(0, 0, 0.15, 1);
```

## Checklist

- [x] Included code example that can be used to test this change
- [x] 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.

None yet

4 participants