react-native-reanimated icon indicating copy to clipboard operation
react-native-reanimated copied to clipboard

Significant performance degradation from 3.6 to 3.7

Open jacobmolby opened this issue 1 year ago • 15 comments

Description

As people also have been writing here: https://github.com/software-mansion/react-native-reanimated/issues/5685 there are serious performance degradations in 3.7 compared to 3.6.

I've made an example to reproduce it. It's a list that has a Gesture, 1 SharedValue, 2 useAnimatedStyle (each with an interpolateColor).

I've ran the example through Flashlight to see the metrics. It's seems that the UI thread is a lot more busy in 3.7. I think the report is a little misleading in regards to the JS thread, since 3.7 lags so much that fewer items are loaded in the list.

I can imagine it has something to do with this commit but I'm not sure.

overview fps

Steps to reproduce

Also provided in the reproduction:

import randomColor from 'randomcolor';
import React, { useMemo } from 'react';
import { FlatList, View } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
  interpolateColor,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';
import tinycolor from 'tinycolor2';

const ITEM_HEIGHT = 150;

const data = Array.from({ length: 150 }, (_, i) => i + 1);

const TestScreen = () => {
  return (
    <View style={{ flex: 1, backgroundColor: 'white' }}>
      <FlatList
        data={data}
        keyExtractor={item => item.toString()}
        renderItem={({ item }) => <RenderItem item={item} />}
      />
    </View>
  );
};

export default TestScreen;

const RenderItem = ({ item }) => {
  const color = randomColor({ seed: item });

  const pressIn = useSharedValue(0);

  const longPressGesture = useMemo(() => {
    return Gesture.LongPress()
      .minDuration(250)

      .onBegin(() => {
        pressIn.value = withTiming(1, { duration: 100 });
      })
      .onFinalize(() => {
        pressIn.value = withTiming(0, { duration: 200 });
      });
  }, [pressIn]);

  const isLight = useMemo(() => {
    return tinycolor(color).isLight();
  }, [color]);

  const containerStyle = useAnimatedStyle(() => {
    return {
      backgroundColor: interpolateColor(
        pressIn.value,
        [0, 1],
        [color, isLight ? 'black' : 'white']
      ),
    };
  }, [color, isLight]);

  const textStyle = useAnimatedStyle(() => {
    return {
      color: interpolateColor(
        pressIn.value,
        [0, 1],
        [isLight ? 'black' : 'white', isLight ? 'white' : 'black']
      ),
    };
  }, [color, isLight]);

  return (
    <GestureDetector gesture={longPressGesture}>
      <Animated.View
        style={[
          containerStyle,
          {
            height: ITEM_HEIGHT,
            alignItems: 'center',
            justifyContent: 'center',
          },
        ]}>
        <Animated.Text style={textStyle}>{item}</Animated.Text>
      </Animated.View>
    </GestureDetector>
  );
};

Setup

  1. Install the dependencies: npm install
  2. Install Flashlight: https://docs.flashlight.dev/
  3. Install Maestro: https://maestro.mobile.dev/getting-started/installing-maestro

To run for for reanimated 3.6.3

  1. npm install [email protected]
  2. npm run android:release
  3. flashlight test --bundleId com.anonymous.reanimatedperfissue \
       --testCommand "maestro test scroll.yml" \
       --duration 30000 \
       --iterationCount 5 \
       --resultsFilePath 3_6_3.json --resultsTitle "3.6.3"
    

Not the actual flashlight test

https://github.com/software-mansion/react-native-reanimated/assets/25974867/76fc50a2-0b59-4b4a-a325-8bc426c4e7a0

To run for for reanimated 3.7.2

  1. npm install [email protected]
  2. npm run android:release
  3. flashlight test --bundleId com.anonymous.reanimatedperfissue \
       --testCommand "maestro test scroll.yml" \
       --duration 30000 \
       --iterationCount 5 \
       --resultsFilePath 3_7_2.json --resultsTitle "3.7.2"
    

Not the actual flashlight test

https://github.com/software-mansion/react-native-reanimated/assets/25974867/3497096a-c515-4dbf-bbf9-481cbc8004c5

Snack or a link to a repository

https://github.com/jacobmolby/reanimated-perf-issue

Reanimated version

3.7.2

React Native version

0.73.6

Platforms

Android

JavaScript runtime

Hermes

Workflow

Expo Dev Client

Architecture

Paper (Old Architecture)

Build type

Release app & production bundle

Device

Real device

Device model

Samsung Galaxy A52G (Android 14)

Acknowledgements

Yes

jacobmolby avatar Mar 21 '24 07:03 jacobmolby

I was preparing a demo to report the same issue, We had to revert to version 3.6.1.

I think this is where the problem is coming from commit. this issue occurs if there is an ongoing animation and the component is unmounted from the screen.

You are also using FlastList, so the parts that are not visible on the screen are unmounted. I think this is why the same problem occurs.

I'm adding another demo. (v3.6.1 VS v3.8.1)

I had to push the limits a bit to show the problem more clearly (with rendering 1000 animated views)

https://github.com/software-mansion/react-native-reanimated/assets/13810383/a535a5ce-e591-4337-9014-92105da852fd

https://github.com/software-mansion/react-native-reanimated/assets/13810383/c9623a07-452f-4879-a0ea-0f8a918e8f61

import React, {useEffect, useState} from 'react';
import {
  Text, TouchableOpacity, View,
} from 'react-native';
import Animated, {useAnimatedStyle, useSharedValue, withRepeat, withTiming} from "react-native-reanimated";


const App = () => {
  const [screen, setScreen] = useState("A");

    const ScreenA = () => {
        const rotation = useSharedValue(0);
        const animatedStyle = useAnimatedStyle(() => {
            return {
                transform: [{rotate: `${rotation.value}deg`}]
            }
        })

        useEffect(() => {
            rotation.value = withRepeat(withTiming(270, {duration: 1000}), -1, true)
        }, []);

        return(
            <View style={{flex:1, justifyContent: "center", alignItems: "center"}}>
                <Text>This is Screen A</Text>
                <TouchableOpacity style={{backgroundColor: "red"}}  onPress={() => setScreen("B")}>
                    <Text>Go to Screen B</Text>
                </TouchableOpacity>

                <View style={{flexDirection: "row", marginTop: 100}}>
                    {[...Array(1000)].map((value, index, array) => {
                        return <Animated.View key={index} style={[animatedStyle, {width: 50, height: 50, backgroundColor: "blue"}]}/>;
                    })}
                </View>
            </View>
        );
    }
    const ScreenB = () => {

        const rotation = useSharedValue(0);
        const animatedStyle = useAnimatedStyle(() => {
            return {
                transform: [{rotate: `${rotation.value}deg`}]
            }
        })

        useEffect(() => {
            rotation.value = withRepeat(withTiming(270, {duration: 1000}), -1, true)
        }, []);

        return(
            <View style={{flex:1, justifyContent: "center", alignItems: "center"}}>
                <Text>This is Screen B</Text>
                <TouchableOpacity style={{backgroundColor: "red"}} onPress={() => setScreen("A")}>
                    <Text>Go to Screen A</Text>
                </TouchableOpacity>


                <View style={{flexDirection: "row", marginTop: 100}}>
                    {[...Array(1000)].map((value, index, array) => {
                        return <Animated.View key={index} style={[animatedStyle, {width: 50, height: 50, backgroundColor: "red"}]}/>;
                    })}
                </View>
            </View>
        );
    }

  return (
    <View style={{flex:1}}>
      {screen === "A" && <ScreenA/>}
      {screen === "B" && <ScreenB/>}
    </View>
  );
}
export default App;

ozgursoy avatar Mar 21 '24 10:03 ozgursoy

probably related to: https://github.com/software-mansion/react-native-reanimated/issues/5800

efstathiosntonas avatar Apr 10 '24 10:04 efstathiosntonas

I've also noticed the performance loss in my library with the recent versions in particular for low end android devices when executing callbacks with runOnJs, by using the supported version by Expo SDK 50 it works smoothly, upgrading to Expo SDK 51 the performance loss is significant.

Glazzes avatar May 18 '24 21:05 Glazzes

@tjzel @piaskowyk @kmagiera I know the whole team is focused on new arch but in the meantime can you please investigate/keep in mind these performance issues while you're on it? There are plenty of issues hanging around in here and in Moti library repo.

efstathiosntonas avatar May 29 '24 06:05 efstathiosntonas

@efstathiosntonas We definitely have this in mind, some time ago I have even been planning some stress tests in Reanimated. Once we are done with current matters we will jump to this.

tjzel avatar May 29 '24 09:05 tjzel

This issue blocks our update to Expo 51.

yolpsoftware avatar Jun 05 '24 22:06 yolpsoftware

Not sure if related, but our old issue #4978 is once more an issue in Expo 51 with Reanimated 3.7 and upwards.

Filed a new bug #6083.

yolpsoftware avatar Jun 06 '24 07:06 yolpsoftware

I have the same issue and it blocks upgrade to RN 0.74. I understand you have a lot of tasks to do, but I hope this issue will not be ignored.

Rag0n avatar Jun 07 '24 16:06 Rag0n

Just come across this issue also upgrading to Expo 51. Downgraded to 3.6.2 initially for stability. 3.8.0 seemed to also be workable. Fine for iOS, not so great for Android (note: Deprecated Gradle features...).

LeslieOA avatar Jun 19 '24 19:06 LeslieOA

Isn't this fixed, now that #6083 is fixed?

yolpsoftware avatar Jul 20 '24 04:07 yolpsoftware

Isn't this fixed, now that #6083 is fixed?

It's definitely better.

I tried to run my initial test with 3.14.0.

However the FPS drops is still greater than version 3.6

image

jacobmolby avatar Jul 23 '24 08:07 jacobmolby

Thanks for running the tests!

At this point it seems that the difference between 3.6.0 and 3.14.0 is of only ~1.7 FPS. From your data we can also see that the average test duration was shorter than in your tests on 3.6.0. Every other metric improved or stayed the same.

I feel like that at this point this could also just be a measurement error?

hannojg avatar Jul 23 '24 09:07 hannojg

Thanks for running the tests!

At this point it seems that the difference between 3.6.0 and 3.14.0 is of only ~1.7 FPS. From your data we can also see that the average test duration was shorter than in your tests on 3.6.0. Every other metric improved or stayed the same.

I feel like that at this point this could also just be a measurement error?

Yeah, I think I've might been a bit too fast making the post. I realized that the old tests (I didn't rerun those) were made with a different device, I forgot since it's been so long.

I'll redo all the tests with the same device and post the results

jacobmolby avatar Jul 23 '24 10:07 jacobmolby

Even though 3.6.3 scores the lowest, it still seems it has far greater performance. You can barely see the line on the chart since it at 60 FPS the whole time. I lowered the test duration to 18 seconds to avoid the trailing end where it was showing a static screen (bottom of the list).

image

jacobmolby avatar Jul 23 '24 10:07 jacobmolby

Just come across this issue also upgrading to Expo 51. Downgraded to 3.6.2 initially for stability. 3.8.0 seemed to also be workable. Fine for iOS, not so great for Android (note: Deprecated Gradle features...).

same with me Configure project :react-native-reanimated Android gradle plugin: 8.2.1 Gradle: 8.6

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

sawankumar1012 avatar Jul 26 '24 08:07 sawankumar1012