software-mansion / react-native-gesture-handler

Declarative API exposing platform native touch and gesture system to React Native.
https://docs.swmansion.com/react-native-gesture-handler/
MIT License
6.13k stars 982 forks source link

Optimise `ReanimatedSwipeable` component #3165

Closed latekvo closed 2 weeks ago

latekvo commented 1 month ago

Description

Test plan

Test code

Tested on @shopify/flash-list using their useBenchmark utility hook.

Collapsed code ```js import { Text, StyleSheet, View, Alert } from 'react-native'; import ReanimatedSwipeable from 'react-native-gesture-handler/ReanimatedSwipeable'; import { FlashList, ListRenderItem, useBenchmark } from '@shopify/flash-list'; import { memo, useRef } from 'react'; function RightAction() { return Text; } type Item = { id: string; title: string; }; const generateItems = (count: number): Item[] => { return Array.from({ length: count }, () => { const randomNumber = Math.random().toString(36).substring(2, 8); return { id: randomNumber, title: `Title ${randomNumber}`, }; }); }; const data = generateItems(200); const _RenderItemView = (item: Item) => ( {item.title} ); const RenderItemViewMemoed = memo(_RenderItemView); export default function Example() { const renderItem: ListRenderItem = ({ item }) => { return ( ); }; const flashListRef = useRef | null>(null); useBenchmark(flashListRef, (callback) => { Alert.alert('result', callback.formattedString); }); return ( item.id} /> ); } const styles = StyleSheet.create({ leftAction: { width: 50, height: 50, backgroundColor: 'crimson' }, rightAction: { width: 50, height: 50, backgroundColor: 'purple' }, separator: { width: '100%', borderTopWidth: 1, }, swipeable: { height: 50, backgroundColor: 'papayawhip', alignItems: 'center', }, }); ```
latekvo commented 1 month ago

Performance tests.

format: fps | changes in relation to the previous run < comment

tested on: Xiaomi Redmi Note 8T:

1.0 | baseline
1.6 | remove all returned elements but the view with children nested
2.0 | remove animated styles < less stutters
2.6 | remove all unused variables
4.6 | remove all unused variables < large leap
4.8 | remove all unused variables
6.2 | delete unused imports < large leap + less stutters
6.5 | stop changing methods ref every render < done in 104761f
10.7 | remove all remaining unused declarations < large improvement
11.7 | remove everything from render besides Animated.View with kids inside
11.3 | remove everything including types, cleanup, leave just RN View with children
11.9 | remove forward ref wrapping everything
11.7 | nothing remains except view with styles

tested on: Xiaomi POCO X3 Pro:

6.3 | baseline
8.5 | remove all returned components except for animated.view with styles and children
10.4 | remove animated styles from animated.view < loads instantly instead of ~1s
11.1 | remove all unused variables
21.4 | remove all unused variables < same large leap as Redmi Note 8T
- 10.9 | revert
- 15.6 | remove updateAnimatedEvent callback < 43% improvement 
- 19.6 | remove handleRelease callback < 25% improvement
50.9 | remove all remaining unused things until none remain
50.2 | nothing remains except view with styles < to see top possible performance

Based on these tests, I believe no improvement past 2x is realistic, which still likely won't be satisfactory. Majority of the issues are unavoidable.

Note:

There are discrepancies between the baseline here, and the baseline listed in the Test Plan. These tests were performed around 5 days apart. I'm not entirely sure why these discrepancies occur, reverting to older commits did not seem to revert to better performance, regardless of that, the relative distances between the values listed in these tests are consistant between different devices, so there isn't a point in redoing them to match current device performance.

m-bert commented 3 weeks ago

I don't understand this part:

2.6 | remove all unused variables 4.6 | remove all unused variables < large leap 4.8 | remove all unused variables

How did you manage to remove all unused variables, and then do this again and again?

latekvo commented 3 weeks ago

How did you manage to remove all unused variables, and then do this again and again?

Upon removing unused values, new, previously used values become unused. By removing unused values via VSC's remove all unused declarations functionality, same heaps of unused declarations can be consistently removed. This helps with sifting out which variables/functions are associated with the largest fps changes.

As can be seen, the second remove all unused variables step causes the largest fps improvement among the early steps, this is due to removal of updateAnimatedEvent and handleRelease callbacks (43% and 25% improvement respectively), unfortunately neither of these functions had any glaring issues, and i don't think much can be done to optimise them.

m-bert commented 3 weeks ago

Upon removing unused values, new, previously used values become unused.

Yes, I get that. The thing is, in such report I'd expect only one entry about removing unused variables, in which whole chain is removed, not just first layer 😅

pranavbabu commented 1 week ago

@latekvo Thanks for the PR and for fixing this issue!