For the last few days I've been fighting performance issues of FlatList inside application I've created.
FlatList consists of static header and x rows. In the measured case there are 62 rows, every one of them consists of 4 components - one of them is used 4 times, which sums up to 7 row cells. Every cell of this list is either TouchableNativeFeedback or TouchableOpacity (for testing purpose those have attached () => null
to onPress. There is shouldComponentUpdate used on few levels (list container, row, single cell) and I think render performance is good enough for that kind of list.
For consistent measurements I've used initialNumToRender={data.length}
, so whole list renders at once. List is rendered using button, data loading isn't part of my measurement - it's preloaded to component local state.
According to attached Chrome Performance Profile JS thread takes 1.33s to render component. I've used CPU slowdown x6 to simulate Android device more accurately.
However, list shows up on device at around 15s marker, so actual render from button press to list showing up takes more than 14s!
What I am trying to figure out is what happens between JS rendering component and component actually showing up on screen, because device is unresponsive whole that time. Every touch event is registered, but it is played out only when the list is finally on screen.
I've attached trace from chrome dev tools, systrace taken using android systrace tool and screen from android profiler (sadly I couldn't find option to export the latter).
Tracing was run almost simultaneously - the order is systrace, android profiler, chrome dev tools.
What are the steps that I should make to help me understand what's going on while the app freezes?
Simple reproduction application (code in src.js, first commit)
Chrome Performance Profile
Systrace HTML
Android Profiler
Performance problems are workplace issues caused by the performance of an individual. This implies a situation that calls for management action such as a performance improvement plan, disciplinary action or dismissal. Performance problems are assessed according to the recent contributions of an individual and the nature of their role.
Performance problems are assessed according to the recent contributions of an individual and the nature of their role. For example, a salesperson who takes very long lunch breaks probably isn't an issue as long as they are closing sales, following up with customers and generally fulfilling their role.
Identifying such performance issues may require qualitative evaluations by a supervisor and may be measurable as customer satisfaction ratings. A software developer who ignores emails and skips meetings to the extent that it creates project issues. A trader at a bank who violates financial regulations.
Remember that you’re talking to your employee about their poor performance. You’re not going to fire that person, and you want them to still contribute to your company. Don’t go into a meeting with a confrontational tone. Don’t be angry either. Try to be calm, cool, and collected and let your employee know what they’re doing wrong. 03.
I was trying different things for a while now, I was even considering wrapping native Android RecyclerView, but to be fair, it seemed quite a challenge as I've had no prior experience with native Android code.
One of the things I've tried over the past few days was using react-native-largelist, but it didn't deliver promised performance improvement. To be fair, it might've been even slower than FlatList
, but I didn't take exact measurements.
After few days of googling, coding and profiling I've finally managed to get my hands on this Medium post, which references recyclerlistview package and it seems to provide better experience than FlatList. For profiled case rendering time dropped to about 2s, including 300ms of JS thread work.
It's mandatory to notice that improvement of initial rendering comes from reduced number of items rendered (11 in my case). FlatList
with initialNumToRender={11}
setting renders initially in about the same time.
In my case initial rendering, while still important, isn't the only thing that matters. FlatList
performance drops for larger lists mainly because while scrolling it keeps all rendered rows in memory, while recyclerlistview
recycles rendered rows putting in new data.
Reason of observer performance improvement for re-rendering is actually simple to test. I've added console.log
to shouldComponentUpdate
in my row component and counted how many rows is actually re-rendered. For my row height and test device resolution recyclerlistview
re-renders only 17 rows, while FlatList
triggers shouldComponentUpdate
for every item in dataset. It's also worth noticing that number of rows re-rendered for recyclerlistview
is not dependent on dataset size.
My conclusion is that FlatList
performance may degrade even more with bigger datasets, while recyclerlistview
speed should stay at similar level.
TouchableNativeFeedback
inside recyclerlistview
also seem to be more responsive, as the animation fires up without delay, but I can't explain that behaviour looking at profilers.
There's definitely still room for improvement in my row component, but for now I am happy with overall list rendering performance.
Simple reproduction application with recyclerlistview (code in src.js, second commit)
Chrome Performance Profile (recyclerlistview)
Systrace HTML (recyclerlistview)
Looking at the source code you posted, I don't think this is a React rendering problem.
The issue is that you're doing too much work in your render
method and the helper methods you are calling during the render pass.
Every time you call .filter
, .forEach
or .map
on an array, the entire list is iterated over n
times. When you do this for m
components, you get a computational complexity of O(n * m)
.
For example, this is the TransportPaymentsListItem
render method:
/**
* Render table row
*/
render() {
const {
chargeMember,
obligatoryChargeNames,
sendPayment,
eventMainNavigation
} = this.props;
/**
* Filters obligatory and obligatory_paid_in_system charges for ChargeMember
*/
const obligatoryChargesWithSys = this.props.chargeMember.membership_charges.filter(
membershipCharge =>
membershipCharge.type === "obligatory" ||
membershipCharge.type === "obligatory_paid_in_system"
);
/**
* Filters obligatory charges for ChargeMember
*/
const obligatoryCharges = obligatoryChargesWithSys.filter(
membershipCharge => membershipCharge.type === "obligatory"
);
/**
* Filters obligatory adjustments for ChargeMember
*/
const obligatoryAdjustments = this.props.chargeMember.membership_charges.filter(
membershipCharge =>
membershipCharge.type === "optional" &&
membershipCharge.obligatory === true
);
/**
* Filters obligatory trainings for ChargeMember
*/
const obligatoryTrainings = this.props.chargeMember.membership_charges.filter(
membershipCharge =>
membershipCharge.type === "training" &&
membershipCharge.obligatory === true
);
/**
* Filters paid obligatory adjustments for ChargeMember
*/
const payedObligatoryTrainings = obligatoryTrainings.filter(
obligatoryTraining =>
obligatoryTraining.price.amount ===
obligatoryTraining.amount_paid.amount
);
// This one does a .forEach as well...
const sums = this.calculatedSums(obligatoryCharges);
// Actually render...
return (
In the sample code there are 11 such iterator calls. For the dataset of 62 rows you have used, the array iterators are called total of 4216 times! Even if every iterator is very simple comparison, simply iterating through all those lists is too slow and blocks the main JS thread.
To solve this, you should lift the state transformation higher up in the component chain, so that you can only do the iteration once and build up a view model that you can pass down the component tree and render declaratively without additional work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With