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

Significant performance degradation from 3.6 to 3.7 #5816

Open
jacobmolby opened this issue Mar 21, 2024 · 15 comments
Open

Significant performance degradation from 3.6 to 3.7 #5816

jacobmolby opened this issue Mar 21, 2024 · 15 comments
Labels
Platform: Android This issue is specific to Android Repro provided A reproduction with a snippet of code, snack or repo is provided

Comments

@jacobmolby
Copy link

jacobmolby commented Mar 21, 2024

Description

As people also have been writing here: #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

recording_3-6-3.mp4

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

recording_3-7-2.mp4

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

@github-actions github-actions bot added Repro provided A reproduction with a snippet of code, snack or repo is provided Platform: Android This issue is specific to Android labels Mar 21, 2024
@ozgursoy
Copy link

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)

v3.8.1.mp4
v3.6.1.mp4
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;

@efstathiosntonas
Copy link
Contributor

probably related to: #5800

@Glazzes
Copy link

Glazzes commented May 18, 2024

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.

@efstathiosntonas
Copy link
Contributor

efstathiosntonas commented May 29, 2024

@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.

@tjzel
Copy link
Collaborator

tjzel commented May 29, 2024

@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.

@yolpsoftware
Copy link

This issue blocks our update to Expo 51.

@yolpsoftware
Copy link

yolpsoftware commented Jun 6, 2024

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.

@Rag0n
Copy link

Rag0n commented Jun 7, 2024

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.

@LeslieOA
Copy link

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...).

@yolpsoftware
Copy link

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

@jacobmolby
Copy link
Author

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

@hannojg
Copy link
Contributor

hannojg commented Jul 23, 2024

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?

@jacobmolby
Copy link
Author

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
Copy link
Author

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

@sawankumar1012
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Platform: Android This issue is specific to Android Repro provided A reproduction with a snippet of code, snack or repo is provided
Projects
None yet
Development

No branches or pull requests

10 participants