maui icon indicating copy to clipboard operation
maui copied to clipboard

[iOS/Android] CollectionView scrolling performance worse in .NET MAUI when compared to Xamarin.Forms (lag/stutter)

Open eth-ellis opened this issue 1 year ago • 27 comments
trafficstars

Description

When migrating our app from Xamarin.Forms to .NET MAUI we noticed that for most of our CollectionViews the performance when scrolling was worse.

When new views appeared the CollectionView would stutter/jitter resulting in an unpleasant user experience.

Our item templates are somewhat complex but still worked great in Xamarin.Forms.

In the linked repro apps, we have a template called CardWithComplexContent which is somewhat similar to the template in our app.

Note

  • In the .NET MAUI project we are using nightly build 8.0.20-nightly.10367
    • This includes https://github.com/dotnet/maui/pull/21229
  • We are using Release configuration when testing performance

Android (Samsung A73) - Xamarin.Forms

https://github.com/dotnet/maui/assets/13865151/25eac924-a499-4908-afd9-ab7108d71437

Android (Samsung A73) - MAUI

https://github.com/dotnet/maui/assets/13865151/cd402208-674f-4cd4-8a67-b3038e1e8f88

iOS (iPhone 6s) - Xamarin.Forms

https://github.com/dotnet/maui/assets/13865151/13a64912-3daf-4325-9d0e-4c15d4cee2eb

iOS (iPhone 6s) - MAUI

https://github.com/dotnet/maui/assets/13865151/d46972ec-5e51-479d-aa53-81351874db9e

Steps to Reproduce

  1. Checkout .NET MAUI and Xamarin.Forms repro projects.
  2. Build and deploy the apps to your devices in Release configuration.
  3. Run both apps and select either the CardWithTheLot or CardWithComplexContent template.
  4. Compare scrolling performance on both apps (fast scrolling and slow scrolling).

The apps contain other templates for attempting to isolate elements which impact performance the most.

Accepting PRs or suggestions on the repro app repos for templates to add for comparison.

Acceptance Criteria

The scrolling experience on .NET MAUI performs as well as or better than Xamarin.Forms for all affected platforms.

Link to public reproduction project repository

https://github.com/eth-ellis/Issue-Repro/tree/main/CollectionViewPerformanceXamarin and https://github.com/eth-ellis/Issue-Repro/tree/main/CollectionViewPerformanceMaui

Version with bug

Nightly / CI build (Please specify exact version)

Is this a regression from previous behavior?

Yes, this used to work in Xamarin.Forms

Last version that worked well

Unknown/Other

Affected platforms

iOS, Android

Affected platform versions

No response

Did you find any workaround?

No response

Relevant log output

No response

eth-ellis avatar Apr 02 '24 15:04 eth-ellis

Unfortunately I must agree. I just put 100 same size labels in one collectionView and it's stuttering on my Pixel 5. In Forms this was totally fine. My workaround is now putting this in VerticalStackLayout and ScrollView as I don't need the other extras. I loads slower, but better than this scrolling experience. Bonus points: put this CollectionView inside a CarouselView and you're crying.

Matt-17 avatar Apr 02 '24 20:04 Matt-17

Thank you for making this thread and providing such objective evidence. Your post clearly shows this is a Maui problem.

I have been struggling with this problem as well. I even built my own version of a "CollectionView" which performs similarly poorly. I have also created simple simulations of translation/update of elements to try to figure out the issue, all of which behave the same.

I have tried two methods of creating/simulating my own "CollectionView" type function in Maui:

  1. Having 10-15 elements that each translate directly themselves together on screen and then wrap top to bottom to simulate scrolling. I posted a simple simulation of this nature here. The elements are derived from AbsoluteLayout and hold each only Border/Image/Label elements.

  2. Having 10-15 similar elements on a large AbsoluteLayout as a base which translates (so I am not translating the elements themselves each frame but rather their big parent AbsoluteLayout) and then wrapping/updating the necessary elements only as they hit screen top or bottom.

In both cases the performance on Android has been identical to what you show in your videos. Even if I simplify the display elements so they are just a few labels on an AbsoluteLayout, Android struggles to update them effectively in Maui. I can't build release iOS versions to compare.

Given I can reproduce the same stuttery "scrolling" even with simple creations made of nothing more than AbsoluteLayout, Image, Label, and Border with very simple updates, I suspect the problem is more generalized in Maui than the CollectionView.

I suspect it has something to do with Maui invalidating the platform views in a way that is making them redraw the screen too many times when even simple updates occur. There is clearly way too much effort going on. Translations should be extremely cheap to adjust and Label text updates should also be very simple. Yet they are not. Simple things like this cause the apps to grind down. Watching the Android Studio profiler when I load a test APK, I can see it is doing too much work each update but don't know why.

Hopefully your evidence will help lead to the cause and solution.

jonmdev avatar Apr 03 '24 02:04 jonmdev

Verified this issue with Visual Studio 17.10.0 Preview 2(8.0.14 & 8.0.0-rc.2.9530). Can repro on android and iOS platforms with sample project.

Zhanglirong-Winnie avatar Apr 03 '24 05:04 Zhanglirong-Winnie

I am converting all my collectionviews for BindableLayouts ( since I don't have that many elements) and the performance gains are huge.

On other note, one thing I noticed: Without interacting with the collectionview, the Garbage Collector is crying, spamming multiple GCs - which is kinda strange? (after loading the CV)

bcaceiro avatar Apr 03 '24 08:04 bcaceiro

this is all about new layout system in MAUI. xamarin forms used another layout system. if look into the code of collectionview in maui and in xf, they are pretty the same. but the layout system had significant changes not in a good way.

By the way, just created own virtualize listview based on scrollview and absolute layout. If somebody wants, can share the code. it does not use any row of native code.

this is android, on ios almost the same. also works on other platfroms

https://github.com/dotnet/maui/assets/23138430/182d5476-90e0-46fa-82b0-c475777148e1

Alex-Dobrynin avatar Apr 04 '24 17:04 Alex-Dobrynin

@Alex-Dobrynin I'd be interested to look at the code. Thank you.

MichaelShapiro avatar Apr 04 '24 18:04 MichaelShapiro

this is all about new layout system in MAUI. xamarin forms used another layout system. if look into the code of collectionview in maui and in xf, they are pretty the same. but the layout system had significant changes not in a good way.

By the way, just created own virtualize listview based on scrollview and absolute layout. If somebody wants, can share the code. it does not use any row of native code.

I am confused and interested by your post. I also agree with you this is not a specific "CollectionView" issue but rather a general Maui issue. As I noted, I created my own "CollectionView" type systems, but they all performed equally poorly.

The problem cannot be directly in the "CollectionView" as you note, but rather inefficiencies in how Maui is updating the elements/layouts or invalidating the views and forcing excess redraws. Even if the only elements I use are AbsoluteLayout elements (containing a few Borders/Labels/Images) translating on an AbsoluteLayout base (simplest Layout system possible) the problem persists.

For example, @jonathanpeppers found one problem here with Labels being slow to update and fixed it: https://github.com/dotnet/maui/pull/21291

But I presume there are other major problems still. Do you know of any specific problem with the Layout that might be causing all of this?

Also are you saying you found a way to circumvent this? If the problem is with the core elements/layout system then I am not sure how it can be circumvented without skipping Maui altogether. Perhaps the problem is just not as visible with a simple custom ListView but without fixing the underlying cause it will still be there of course.

jonmdev avatar Apr 04 '24 21:04 jonmdev

@Alex-Dobrynin I would also be very interested to view the code.

brentpbc avatar Apr 05 '24 02:04 brentpbc

@Alex-Dobrynin me too

FM1973 avatar Apr 05 '24 06:04 FM1973

@Alex-Dobrynin me too

WebGoose avatar Apr 05 '24 08:04 WebGoose

@Alex-Dobrynin looks pretty good and really fast!

IrynaDoroshenkoDev avatar Apr 05 '24 09:04 IrynaDoroshenkoDev

this is not finished version, but it supports different scroll orientations and device orientation change. still working on it.

VirtualizeListView.zip

Check in the release mode, debug is not smooth enough

By the way, it also can support 2d scroll, of course if you implement proper layout manager. for now only linear

Alex-Dobrynin avatar Apr 05 '24 19:04 Alex-Dobrynin

this is all about new layout system in MAUI. xamarin forms used another layout system. if look into the code of collectionview in maui and in xf, they are pretty the same. but the layout system had significant changes not in a good way.

I absolutely concur with the others here who are suggesting this has something to do with MAUI specifically and the Layout System. I have built my own CollectionView, and have also used 2 Third party versions (Telerik, Syncfusion) and I get the same exact results in every scenario. After doing all of that, and testing and profiling, and stepping through source... I can see there are issues with sizing and recycling when using certain layouts and templates -- most notably when using images and grid layouts in CollectionView; the performance is terrible when scrolling, no mater what vendor or version of CollectionView used. There have been a few existing open and backlogged issues about this for a while, and everything seems to be related, so I'm glad this is finally being looked in to.

mjsb212 avatar Apr 07 '24 07:04 mjsb212

Hi,

I've been grappling with an issue that is significantly impacting the performance of our app, much to the frustration of the business owner. The problem lies with our Maui implementation, where we have a grouped list with headers and items, and a pagination feature to load additional data.

This performance issue is surprising to me, especially since I have prior experience with Flutter where I did not encounter such problems. In Flutter, we typically use a scroll view containing a ListView or Column, which automatically triggers data loading when the user scrolls to the bottom. However, attempting a similar setup in Maui has resulted in poor performance.

AliKarimiENT avatar Apr 07 '24 13:04 AliKarimiENT

this is not finished version, but it supports different scroll orientations and device orientation change. still working on it.

VirtualizeListView.zip

Check in the release mode, debug is not smooth enough

By the way, it also can support 2d scroll, of course if you implement proper layout manager. for now only linear

Did you handle paging in here too?

AliKarimiENT avatar Apr 07 '24 13:04 AliKarimiENT

@AliKarimiENT in this version no, this is just POC of smooth scrolling, but on my pc already added cells appering/dissapering functionality and load more. Need to edit layout manager to support this

Alex-Dobrynin avatar Apr 07 '24 13:04 Alex-Dobrynin

@AliKarimiENT in this version no, this is just POC of smooth scrolling, but on my pc already added cells appering/dissapering functionality and load more. Need to edit layout manager to support this

I reviewed the code but I think it will take time to add paging my self, and I will wait to maui update it.

AliKarimiENT avatar Apr 07 '24 14:04 AliKarimiENT

@AliKarimiENT maui will not update it, because this is my implementation. Maui can update only their collectionview

Alex-Dobrynin avatar Apr 07 '24 14:04 Alex-Dobrynin

Thank you for the detailed report and reproduction projects! We're investigating this particular issue, and we'll respond with any findings. CollectionView performance is one of our top priorities, so we are working on a variety of fixes that should positively impact most cases.

samhouts avatar Apr 09 '24 00:04 samhouts

Thank you for the detailed report and reproduction projects! We're investigating this particular issue, and we'll respond with any findings. CollectionView performance is one of our top priorities, so we are working on a variety of fixes that should positively impact most cases.

Hello, please keep in mind that even though MAUI's performance is worse in comparison to Xamarin, in case of CollectionView and ScrollView the performance in MAUI .NET 7 wasn't so bad. In .NET 8 it got much worse, check out https://github.com/dotnet/maui/issues/21554

OvrBtn avatar Apr 09 '24 11:04 OvrBtn

There's a number of factors that may compound here causing this particular sample to be somewhat slower. We are continuing to investigate these and look at introducing fixes and improvements. Unfortunately this isn't a simple one change fixes everything.

What I can suggest in the meantime is making some changes to your XAML and data model to minimize scenarios that are going to impact performance. For instance, I made some changes to the original sample here: https://github.com/eth-ellis/Issue-Repro/compare/main...Redth:Issue-Repro:main

Generally having a bindable layout inside of a data template is not going to be a fast thing to do. I understand that it worked reasonably well in Xamarin.Forms, and we will be able to get there with MAUI too but for now, a bit of compromise and optimization of your layouts would help unblock this scenario.

Redth avatar Apr 17 '24 13:04 Redth

@Redth you gave just temporary workaround. It takes a lot of time to do such changings in the templates for each list in the app. For example in my app I have a lot of lists, each page with the list with specific data and different templates for the same data types, and regular ListView or CollectionView are suffocating to process my templates and data there.

But here the updated version of the VirtualListView I provided above, where it is much much performant then regular ListView and even CollectionView. And I'm sure it can be optimized much more, but I don't have enough time to play with it, for me it is enough of scrolling performance. And as you can see, there is no any platformspecific code for laying out the items. And because of that it works on all platforms out of the box without specific renderers (handlers) (except android for overscroll).

VirtualizeListView.zip

Also, you can write custom data adapter for grouped lists, and it is easy to add there sticky headers. Also you can add there grid items layout manager, for now only linear. And it supports both orientations and even neither, when user wants to disable scrolling.

Just give it a try and play with it. may be you can take this idea to write your implementations for new CollectionView or optimize this one. But existing CollectionView is overengineering and should be rewritten completely.

Alex-Dobrynin avatar Apr 17 '24 19:04 Alex-Dobrynin

I can confirm

I am working on my own DataGrid with CollectionViews (no infinite scroll only pagination). And I can give you some point of view.

My main ViewModel got the classical ItemsSource

        ItemsSource = new ObservableCollection<RowViewModel>();

each Row got it's own ViewModel

To get to the N page I got two solutions to replace the Items

1. create a local and affect ItemsSource.

var rowViewModels = new ObservableCollection<RowViewModel>();
... //foreach
rowViewModels.Add(new RowViewModel(row, index++)
...
ItemsSource = rowViewModels;

2. Or add directly to the ItemsSource

... //foreach
ItemsSource.Add(new RowViewModel(row, index++)
...

I have encountered latency issues with rendering, causing the screen to freeze my loader. If my loader freezes, it's because the MainThread is handling too much work...

The third approach

However, I found a third approach that works quite well instantly and does not freeze my loader!!!! In my RowViewModel, I have a main object, let's call it Monkey, which raises a PropertyChanged event for each property of the MonkeyRowViewModel.

public class MonkeyRowViewModel : BaseViewModel
{
    public MonkeyRowViewModel(Monkey monkey, int index)
    {
        Monkey = monkey;
        Index = index;
    }

    public Monkey Monkey
    {
        get => _monkey;
        set
        {
            _monkey = value;
            UpdateAll();
        }
    }
    
    public string MonkeyName => Monkey.Name;
    ...
    internal void UpdateAll([CallerMemberName] string name = null)
    {
        var changed = PropertyChanged;
        if (changed == null)
            return;

        var elementType = GetType();
        foreach (var prop in elementType.GetProperties())
            if (name != prop.Name)
                changed.Invoke(this, new PropertyChangedEventArgs(prop.Name));
    }

Now in my Main View Model rather than creating a new ItemsSource I just change the Monkey Object of each Sourced Items

foreach (MonkeyRowViewModel vm in ItemsSource) 
    vm.Monkey = ApiResults.Monkeys.ElementAt(index++)

And this is very fast because I recycle the RowViewModels, I simply change their internal Monkey objects! Under the hood I prevent the collectionView of pushing new/recycled views on the screen.

So the main problem is not the Bindings!

Now it could arise from two main reasons:

1. How CollectionView assign/managed sourced item with new/recycled View.

2. How .NET MAUI render Views on the screen, even with already constructed Views.

The example provided by @Alex-Dobrynin suggests the first reason.

But in another scenario, i suggest the second.

When I want to push a complex View (kind of a popup) in a Grid for example and I use a button to Add/Remove this popup View from the Grid. I can remove the View pretty fast. But adding the View to the Grid depends if your finger is align with the stars ✨. It can be very fast (less than 300ms) or pretty slow (more than 600ms)

Both reasons seems related

@Redth @samhouts @PureWeen and the all team Thank you for your time ❤️

Phenek avatar Apr 18 '24 07:04 Phenek

There's a number of factors that may compound here causing this particular sample to be somewhat slower. We are continuing to investigate these and look at introducing fixes and improvements. Unfortunately this isn't a simple one change fixes everything.

What I can suggest in the meantime is making some changes to your XAML and data model to minimize scenarios that are going to impact performance. For instance, I made some changes to the original sample here: eth-ellis/[email protected]:Issue-Repro:main

Generally having a bindable layout inside of a data template is not going to be a fast thing to do. I understand that it worked reasonably well in Xamarin.Forms, and we will be able to get there with MAUI too but for now, a bit of compromise and optimization of your layouts would help unblock this scenario.

I have not had a "bindable layout inside a data template." I am just doing straight C# and my basic label and translation updates are very slow causing similar lag and delays I don't get with similar builds in .NET Andorid. I think this is a very generalized and broad problem that runs through Maui. As you said, there are likely numerous issues.

I hope you can work on general fixes that will help us all. We all need the fast smooth scrolling users expect and every other UI system provides.

jonmdev avatar Apr 18 '24 08:04 jonmdev

So the main problem is not the Bindings!

Now it could arise from two main reasons:

1. How CollectionView assign/managed sourced item with new/recycled View.

2. How .NET MAUI render Views on the screen, even with already constructed Views.

The example provided by @Alex-Dobrynin suggests the first reason.

But in another scenario, i suggest the second.

When I want to push a complex View (kind of a popup) in a Grid for example and I use a button to Add/Remove this popup View from the Grid. I can remove the View pretty fast. But adding the View to the Grid depends if your finger is align with the stars ✨. It can be very fast (less than 300ms) or pretty slow (more than 600ms)

Both reasons seems related

@Redth @samhouts @PureWeen and the all team Thank you for your time ❤️

There may be multiple reasons all at once, so maybe all these things are true. But I can absolutely agree and verify "bindings" are not the primary issue. I don't use bindings/XAML at all and still see terrible scroll performance in my custom C# "CollectionView" type design (which works blazingly fast and smooth in other non-Maui systems).

I manage my own reassignments of elements. From a C# perspective this is basically instant by stopwatch as this is very simple code in my system. So again, perhaps that is poorly done in Maui CollectionView, but it is not also the main issue.

2. How .NET MAUI render Views on the screen, even with already constructed Views.

This is the primary issue without a doubt. Although I don't want to share my real proprietary code and systems (and they are baked in deep into my projects anyway to excise), I have already posted a simple "autoscroller" that this is the case and can do more if needed.

The C# code that we have to control translation and updates updates can be virtually instant and take 0-1 ms by stopwatch, but Android will then struggle for multiple frames trying to render it all with each update at a terrible frame rate. The only theories or ideas I have:

  • Maui is forcing excess redraws on updates.
  • Simple updates like translations that should be basically instantaneously and "free" are costing way too much time due to wrong functions being called.
  • Simple things like Label text updates are doing way too much and taking too much time.

All of this is generally invisible until you have something scrolling because that is the only time every millisecond counts.

This is a good example of the type of bug that is likely doing this:

https://github.com/dotnet/maui/pull/21291

@jonathanpeppers found that the label text updates were taking way too long because they were getting bogged down in something very slow they didn't need to be doing.

I think we need to evaluate the performance of the core functions for basic inefficiencies like this because that is where the problem likely derives from. ie. Working from the bottom up. Ensure every component can perform equivalently to Xamarin or native under pressure.

For example perhaps we should make .NET Android or Xamarin element torture test projects to compare against MAUI projects? I can make an infinite "autoscroller" in both that randomly updates the entries with labels, but this is only so useful as we don't have equivalent native Border. I don't know Xamarin so that would be a pain in the ass.

One needs to do torture tests of the basic elements against their native .NET or Xamarin equivalents before one can claim the issue is "CollectionView" and not primarily just bad performance in the basic elements. Eg. Have 500 Labels translating randomly on screen spamming with text updates also on a Timer set to 500 fps and see what you get on both for resulting deltaTime on the Timer as the system struggles.

jonmdev avatar Apr 18 '24 08:04 jonmdev

Besides this issue, we have faced a new one on release mode the GroupHeader binding mode doesn't work and we are surprised when we were getting ready to publish it.

AliKarimiENT avatar Apr 20 '24 06:04 AliKarimiENT

Furthermore, I would like to add that: When we change the source of an embedded Image on each items, the scrolling performance on iOS decreases drastically.

As most layouts nowadays require customized images for each item, it would be wise to optimize this aspect.

Phenek avatar May 23 '24 13:05 Phenek

CV rendering performance does affect scrolling in a rather noticeable way, there's simply too much going on on the main thread in a non-optimized way; in connection with an API loader this is far from ideal because it freezes the scroll view when rendering the next bunch of items to display. This is a cross-platform issue causing poor user experience that should be somehow prioritized.

Equabyte avatar May 30 '24 19:05 Equabyte

We've made a number of fixes in SR6

Still keeping this open for SR7 to continue tracking improvements.

If folks can test nightly on Android and report back that'd be helpful

Can you test with the latest nightly build? https://github.com/dotnet/maui/wiki/Nightly-Builds

PureWeen avatar May 31 '24 23:05 PureWeen

Can you test with the latest nightly build? https://github.com/dotnet/maui/wiki/Nightly-Builds

Hi @PureWeen I just tested on Android with 8.0.60-ci.net8.24301.1 and in my case I can see no noticeable improvement. Let me also clarify the actual issue I'm facing with scrolling performance refers to the case of a CollectionView that requires items to be added incrementally as the user scrolls (think of news items read from some API, preloaded and buffered in a non-visible data structure and incrementally moved to a CollectionView). The issue is that, whatever solution I tried to adopt so far, adding (or changing) a few items to the CollectionView causes a short UI freeze that stops the scrolling until the rendering is complete. There seems to be no way to get the CollectionView to update in background while the scrolling happens, so even when I try to entertain the user with empty placeholder items, the scrolling is affected by the UI update in a rather annoying manner while the placeholders get replaced with the actual items. Loading the entire CollectionView prior to showing it to user is not an option in these cases, because the initial wait would take too long, so an incremental loading mechanism is recommended.

Equabyte avatar Jun 01 '24 07:06 Equabyte