react-native-reanimated
react-native-reanimated copied to clipboard
Significant performance degradation from 3.6 to 3.7
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.
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
- Install the dependencies:
npm install - Install Flashlight: https://docs.flashlight.dev/
- Install Maestro: https://maestro.mobile.dev/getting-started/installing-maestro
To run for for reanimated 3.6.3
npm install [email protected]npm run android:release-
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
npm install [email protected]npm run android:release-
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
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;
probably related to: https://github.com/software-mansion/react-native-reanimated/issues/5800
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.
@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 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.
This issue blocks our update to Expo 51.
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.
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.
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...).
Isn't this fixed, now that #6083 is fixed?
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
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?
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
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).
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.