You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It defaults to 2000 pixels unless you override it and with 15 rows it might not go above that and so will render everything when you scroll? This is just a guess but it might be worth tweaking the props for this component to see if you can affect this behaviour.
React Native is using a different optimization strategy than iOS. Here's a summary
Load balancing
In UITableView, when an element comes on screen, you have to synchronously render it. This means that you've got less than 16ms to do it. If you don't, then you drop one or multiple frames. If you are rendering complex elements like newsfeed stories, it's basically impossible to meet this schedule so you're doomed to drop frames.
With ListView, when you reach the end of the current screen, you can prepare in advance more rows to be rendered. Those rows will be rendered in a different thread so won't freeze the UI thread while processing. The reason why it is working is that the load is not evenly spread. You don't need to render a new story on every single frame, most frames are just scrolling and don't need new stories to appear.
ListView will also render one element at a time, so if you are interacting with some element while rendering more rows, it won't block until all the rows have been pre-rendered, it will only block for one row.
Memory management
UITableView is very conservative memory-wise, it aggressively reuses cells. This decision was made back in the iPhone 1 where memory was extremely scarce. The problem with this is that reusing cell is extremely error prone for the developer. You are given a dirty object, from which you have no idea what mutations happened, and you need to reconfigure it to look like what you want. In our iOS app, this caused SOOO many bugs.
The problem of reusing cell is that some cells have internal state (video player running, text input, horizontal scroll position...) When you reuse them, you need to be able to serialize that state and put it back. This is not always possible nor easy, so you usually either loose this state or it propagates on the new row and causes bugs.
What we found out on React Native is that it is fast enough on iphone 4s to create new cells for every single row. So, we don't need to impose this very hard constraint on ourself. In your screenshot, you noticed that we don't remove rows after you scrolled for a while. That's not entirely correct, we don't remove the virtual dom representation on the React side (what you see in the chrome dev tools), but we do remove those elements from the "dom" and keep their reference.
When they are visible again, we put them back on the dom. In case we have low memory or the list is too big, we may destroy those and recreate them from scratch (loosing the state as mentioned above) in the future. We haven't done this performance optimization yet, but the user code wouldn't be impacted.
We tried to delete the iOS views aggressively but we found out that doing so was actually very expensive. It was better to leave them hanging than to remove them.
Change Detection
In ListView, we have a DataSource object that favors immutability. If you have a list of 1000 elements to render, you want to make those 1000 elements immutable, meaning that you can check the previous one === the next one and instantly know if something changed. This way, when anything change, the only thing you've got to do is to traverse those two lists and do those very fast equality checks and know what rows changed. And then update only those.
Layout
In UITableView, you've got to specify the layout of every single row even when they are not being displayed on screen. So, in cases where it's not a fixed size, you've got to basically render the element to know its size, and pay that high cost up front. It's also very annoying to do so manually.
In ListView, since React Native owns the layout system, you don't need to do all that painstaking manual computation yourself. When a row is rendered, it'll update the size. The only downside is that the scrollbar is a little funky, but I'm sure we'll be able to come up with heuristics to smooth it out in the future.
lumiasaki, shaialon, sampurcell93, khanghoang, gre and 38 moreLeoNatan and GevGhzhangwebb, khzliu, shixy and vaskortzhangwebb, khzliu, kissonchan and Jeffliu
Something doesn't seem to be working as intended just yet. After scrolling a very long list view with images, and 4 rows visible in vertical ipad air 2, everything seems to be working as @vjeux describes, except the memory just keeps climbing and climbing. And the most alarming thing is that scrolling (after scrolling to the bottom), can cause main thread to easily eat over 100-130% of cpu, while the JSCore thread stays quiet. This is very different to the 1-3% taken by just JSCore when scrolling from the top after the initial render. It is very apparent that the app is unstable while the listview's rows all remain in memory, and the main thread is blocked for about a second give or take while a listview in this state is unmounted, and can even result in a crash. Just a reminder, this is all on an iPad Air 2.
I also tried the "experimental feature" of removeClippedSubviews for ListView. This initially did seem to help with memory, but it was causing a crash. I assumed this was the "experimental" part, and haven't dug in there just yet ;)
What I'm wondering is if I am just missing something? Do I have the capability to remove images myself using onChangeVisibleRows, should I consider this? Would it be worth more getting removeClippedSubviews stable, or should I start contemplating a new version of list view with recycling?
@vjeux What would make this actionable? I asked a few questions in this thread that was answered by closing the issue? I've also still been digging through the innards of the ListView implementation, as well as the crash caused by removeClippedSubviews: true. The title "ListView renders all rows?" seems appropriate based on memory consumption no?
When I change the data source to contain only one section (same amount of rows), this crash does not occur, and I can see that the views are being clipped.
In this case, the memory is lowered to about 1/2-2/3 what is was before, but still very high, and cpu is not spiking as it did on minimal scroll, also about 1/2-2/3 of what is was before.
Activity
colinramsay commentedon Mar 30, 2015
I wonder if this is related to the default value of scrollRenderAheadDistance:
http://facebook.github.io/react-native/docs/listview.html#scrollrenderaheaddistance
It defaults to 2000 pixels unless you override it and with 15 rows it might not go above that and so will render everything when you scroll? This is just a guess but it might be worth tweaking the props for this component to see if you can affect this behaviour.
vjeux commentedon Mar 30, 2015
React Native is using a different optimization strategy than iOS. Here's a summary
Load balancing
In UITableView, when an element comes on screen, you have to synchronously render it. This means that you've got less than 16ms to do it. If you don't, then you drop one or multiple frames. If you are rendering complex elements like newsfeed stories, it's basically impossible to meet this schedule so you're doomed to drop frames.
With ListView, when you reach the end of the current screen, you can prepare in advance more rows to be rendered. Those rows will be rendered in a different thread so won't freeze the UI thread while processing. The reason why it is working is that the load is not evenly spread. You don't need to render a new story on every single frame, most frames are just scrolling and don't need new stories to appear.
ListView will also render one element at a time, so if you are interacting with some element while rendering more rows, it won't block until all the rows have been pre-rendered, it will only block for one row.
Memory management
UITableView is very conservative memory-wise, it aggressively reuses cells. This decision was made back in the iPhone 1 where memory was extremely scarce. The problem with this is that reusing cell is extremely error prone for the developer. You are given a dirty object, from which you have no idea what mutations happened, and you need to reconfigure it to look like what you want. In our iOS app, this caused SOOO many bugs.
The problem of reusing cell is that some cells have internal state (video player running, text input, horizontal scroll position...) When you reuse them, you need to be able to serialize that state and put it back. This is not always possible nor easy, so you usually either loose this state or it propagates on the new row and causes bugs.
What we found out on React Native is that it is fast enough on iphone 4s to create new cells for every single row. So, we don't need to impose this very hard constraint on ourself. In your screenshot, you noticed that we don't remove rows after you scrolled for a while. That's not entirely correct, we don't remove the virtual dom representation on the React side (what you see in the chrome dev tools), but we do remove those elements from the "dom" and keep their reference.
When they are visible again, we put them back on the dom. In case we have low memory or the list is too big, we may destroy those and recreate them from scratch (loosing the state as mentioned above) in the future. We haven't done this performance optimization yet, but the user code wouldn't be impacted.
We tried to delete the iOS views aggressively but we found out that doing so was actually very expensive. It was better to leave them hanging than to remove them.
Change Detection
In ListView, we have a DataSource object that favors immutability. If you have a list of 1000 elements to render, you want to make those 1000 elements immutable, meaning that you can check the previous one === the next one and instantly know if something changed. This way, when anything change, the only thing you've got to do is to traverse those two lists and do those very fast equality checks and know what rows changed. And then update only those.
Layout
In UITableView, you've got to specify the layout of every single row even when they are not being displayed on screen. So, in cases where it's not a fixed size, you've got to basically render the element to know its size, and pay that high cost up front. It's also very annoying to do so manually.
In ListView, since React Native owns the layout system, you don't need to do all that painstaking manual computation yourself. When a row is rendered, it'll update the size. The only downside is that the scrollbar is a little funky, but I'm sure we'll be able to come up with heuristics to smooth it out in the future.
[-]ListView renders all rows.[/-][+]ListView renders all rows?[/+]luics commentedon Mar 31, 2015
@vjeux thx, it's really clear and useful.
drkibitz commentedon Mar 31, 2015
Something doesn't seem to be working as intended just yet. After scrolling a very long list view with images, and 4 rows visible in vertical ipad air 2, everything seems to be working as @vjeux describes, except the memory just keeps climbing and climbing. And the most alarming thing is that scrolling (after scrolling to the bottom), can cause main thread to easily eat over 100-130% of cpu, while the JSCore thread stays quiet. This is very different to the 1-3% taken by just JSCore when scrolling from the top after the initial render. It is very apparent that the app is unstable while the listview's rows all remain in memory, and the main thread is blocked for about a second give or take while a listview in this state is unmounted, and can even result in a crash. Just a reminder, this is all on an iPad Air 2.
I also tried the "experimental feature" of removeClippedSubviews for ListView. This initially did seem to help with memory, but it was causing a crash. I assumed this was the "experimental" part, and haven't dug in there just yet ;)
What I'm wondering is if I am just missing something? Do I have the capability to remove images myself using
onChangeVisibleRows
, should I consider this? Would it be worth more getting removeClippedSubviews stable, or should I start contemplating a new version of list view with recycling?ide commentedon Mar 31, 2015
Does Instruments provide any insight into what's leaking? (specifically wondering if the problem is in JS or Obj-C)
drkibitz commentedon Mar 31, 2015
@ide I'm a noob at instruments profiler... So here's brief summary from me taking a look at it.
cpu profile looks like most of the time is spent here (recursing through subviews), in RCTView.m :
- (void)react_updateClippedSubviewsWithClipRect:(CGRect)clipRect relativeToView:(UIView *)clipView
In memory profile major causes of persisted memory (645 MB total) are:
Unmount the ListView component, and total persisted memory drops to 74 MB total:
I have to think I'm doing something wrong, or maybe the ListView just isn't prepared for what I'm throwing at it.
drkibitz commentedon Mar 31, 2015
Those profiles were from attaching to iPad Air simulator.
vjeux commentedon Mar 31, 2015
cc @bryceredd who's been investigating React Native performance
vjeux commentedon Apr 1, 2015
Closing this since it's not very actionable.
drkibitz commentedon Apr 1, 2015
@vjeux What would make this actionable? I asked a few questions in this thread that was answered by closing the issue? I've also still been digging through the innards of the ListView implementation, as well as the crash caused by
removeClippedSubviews: true
. The title "ListView renders all rows?" seems appropriate based on memory consumption no?vjeux commentedon Apr 1, 2015
Sorry, trying to clean up the hundred plus issues we had, I was a bit too quick on this one.
drkibitz commentedon Apr 1, 2015
@vjeux No worries, and thanks ;)
What I've found so far regarding the
removeClippedSubviews: true
crashI'm seeing it happen on line 217 of RCTScrollView:
UIView *nextHeader = nextDockedIndex >= 0 ? contentView.subviews[nextDockedIndex] : nil;
When I change the data source to contain only one section (same amount of rows), this crash does not occur, and I can see that the views are being clipped.
In this case, the memory is lowered to about 1/2-2/3 what is was before, but still very high, and cpu is not spiking as it did on minimal scroll, also about 1/2-2/3 of what is was before.
93 remaining items