Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<ScrollView> lags and stutters when many <Label> Text updates happen periodically while trying to scroll on physical iOS device #19938

Open
schlaman opened this issue Jan 17, 2024 · 5 comments
Labels
area-controls-scrollview ScrollView delighter-sc platform/iOS 🍎 s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.)
Milestone

Comments

@schlaman
Copy link

Description

I created a test app to demonstrate that if you attempt to have a <ScrollView> and items to scroll, even just a bunch of labels with no binding or computations/lookups involved then there is significant lag/stuttering IF you frequently updated SomeLabelField.Text value while scrolling.

In my example, I have a <Grid> and I have the <Label> controls that I update with a timer at an interval of 100ms. Even at 500ms you see bad lag.

Must deploy to physical iOS device to demonstrate issue.
This is an issue on a physical iOS device. Not sure about Android. In Windows it is not an issue at all.

Steps to Reproduce

Use the TimerLagTest app I created. It is in the public repro link below.

Deploy to a physical iOS device
The lag is quite noticeable on an iPhone Xs Max and an iPad Mini Gen 6
Deploy as debug (When deployed in Release mode the lag is way less noticeable but still occurs)

  1. Run the App
  2. Scroll the test lines (there are many hundreds of test lines) - Notice it is completely smooth - no jerkyness
  3. Now click the Start Timer button (notice that the status changes to indicate that the timer is running and you also see 3 labels updating.
  4. Now scroll the lines and you can see the lag/jerkiness.
  5. Now click the Update Off button - notice that the labels are not updating (but the timer is still going and all the timer ticks and method calls and DateTime.Now() call is happening. The ONLY thing not happening are the SomeLabel.Text="..." calls.
    Notice that the scrolling now is completely smooth again.

You can see it is not the timer that is causing the jerkiness or the function calls. It is purely the fact that a label is being updated that causes the jerkiness/lag in scrolling. In my real work example I even put in code to multiply like a million numbers in the timer tick event and there was no jerkiness. It is purely the act of just updating a label that KILLS the scrolling performance!!

Link to public reproduction project repository

https://github.com/schlaman/TimerLagTest

Version with bug

8.0.3

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

iOS

Affected platform versions

iOS 17.1

Did you find any workaround?

No workaround found

Relevant log output

No response

@schlaman schlaman added the t/bug Something isn't working label Jan 17, 2024
@schlaman schlaman changed the title <ScrollView> lags and stutters when one or more label updates happen periodically while trying to scroll <ScrollView> lags and stutters when one or more <Label> Text updates happen periodically while trying to scroll Jan 17, 2024
@schlaman schlaman changed the title <ScrollView> lags and stutters when one or more <Label> Text updates happen periodically while trying to scroll <ScrollView> lags and stutters when many <Label> Text updates happen periodically while trying to scroll on physical iOS device Jan 17, 2024
@drasticactions
Copy link
Contributor

drasticactions commented Jan 17, 2024

So looking at this, I think the underlying issue you're having is complex, but IMO it's partially your implementation.

System.Timers.Timer invokes a new background thread. As that's off the UI Thread, you then invoke the UI Thread to update the text. As your example shows, if you turn off updating the actual Labels, the performance for scrolling is fine. However, your performance tanks before you even touch scrolling.

Simulator.Screen.Recording.-.iPhone.15.-.2024-01-17.at.18.17.31.mp4

Using my FPS counter library, I believe your frame rate gets cut in half by updating any part of your UI. The ScrollView itself isn't the issue though. If you remove the scroll view, but keep your component, the total FPS goes down regardless, all be it not as much, to about 45 to 50 FPS. The frame rate issues your seeing has nothing to do with scrolling...

I think the root of your issue is that TestComponent. That isn't a virtualized control, so you have a thousand labels in a stack layout, all rendered at the same time. I think what is happening is that, by you doing that, whenever you invoke an action in the UI to update it, MAUI is recalculating the iOS Constraints for every single label in that StackLayout. Again, this has nothing to do with the ScrollView or scrolling. That's just a byproduct of the FPS being hit by the UI Calculations. Reducing the StackLayout items to 100 or 200, and performance remained at 60 FPS. Above that, it starts to go down...

Now, if I'm correct on that, this might be a bug in MAUI, since that may not be necessary given the context to recalculate all of the iOS Constraints for the underlying views. But, realistically, if you have lots of items like this, you should use a Virtualized List component like ListView or CollectionView. Even if your current implementation works for other platforms, you're probably going to get a good reduction in memory usage by switching to a virtualized control.

Simulator.Screen.Recording.-.iPhone.15.-.2024-01-17.at.18.56.39.mp4

Switching out your StackLayout for a ListView, and performance is rock solid.

@PureWeen What do you think?

@schlaman
Copy link
Author

@drasticactions - Thanks Tim!

I greatly appreciate your time spent reviewing this and your willingness to share your expertise!

One point to note here is my real world use case is I have a large application that has a timer tick every 500ms and I display 3 timers at the top of the page always and at different intervals. I do work like popup reminders every 2 minutes, do autosaves every 3 seconds, refresh various views every 3 seconds...

Where I see the greatly reduced framerate is not while scrolling a list of numbers or strings but just scroll views where there may not be more than just a like 2 screenfuls to scroll - made up of multiple controls nested into one view where each has lots of labels to display with associated values in other labels. I can't do virtualized.

  • The amazing thing is there is enough spare CPU power on the iPhone even the Xs MAX that I can throw in super heavy numeric processing and it doesn't make a dent in framerate but just go and update 1 or 2 labels text field and then it kills it.**

Something just feels very wrong there and I have no foreseeable workaround.

Thanks again for all your assistance and time!

I remain a massive fan of .NET Maui and have been a .NET developer for over 20 years now. Before that, 20 years of C and C++ development.

Thanks for helping move .NET Maui forward!

Craig

@schlaman
Copy link
Author

Is there a recommendation for a different timer to use within the .NET Maui Shell app as a timer a couple times a second to drive periodic tasks and to update/refresh the UI, do autosaves, etc that will not have the same frame rate impact?
Thanks!
Craig

@drasticactions
Copy link
Contributor

@schlaman Your issue isn't the Timer. You could switch it to any of the other .NET Timers and you would hit the same issue. If you remove the huge StackLayout and rerun the project with my FPS Viewer, it would be at 60fps. Again, the timer is not the issue. Nor is the ScrollView.

The issue, as far as I can tell, is having an extremely large set of UI objects loaded while then manipulating items on the UI Thread.

MAUI uses UIKit for building its UI for iOS and Mac (Via Catalyst), and it uses the UIKit AutoLayout system for handling layout between native views. With your current layout, MAUI is creating tons of UILabel/UITextView objects and then building constraints between them. When you then update your top labels with new text, this, I believe (I could be wrong here), causes a cascading effect where every constraint is then being recalculated and redrawn. Since you have thousands of items, all loaded in memory and on the page, while then doing updates to the UI every 100 milliseconds, that causes the performance hit (That's why the FPS goes down without you touching anything. It's a lot of CPU activity). It's also why it only happens in iOS and not Android or Windows, since those handle native layout very differently.

This is why I said this may be a bug in MAUI (if my understanding of the issue is correct, which it may not be), since it probably doesn't need to recalculate and layout the entire UI by calling on UIKit to do so. But in a real world application, having that many objects loaded in a ScrollView as in your sample, IMO, isn't realistic. If you are building lists similar to how you wrote it in your sample, switching to a ListView or CollectionView should get you a performance and memory boost by having it be virtualized.

It may help to make a new sample with a more realistic version of the UI you're building (and why it can't use a ListView or CollectionView) may help to where there are issues in MAUI.

CC @tj-devel709, as I think you know iOS far better than I do, maybe you have ideas?

@ninachen03
Copy link

Verified this issue with Visual Studio 17.10.0 Preview 4 (8.0.0-rc.2.9530 & 8.0.20).I can repro it on iOS platform

@ninachen03 ninachen03 added s/verified Verified / Reproducible Issue ready for Engineering Triage s/triaged Issue has been reviewed labels Apr 18, 2024
@Eilon Eilon added t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.) area-layout StackLayout, GridLayout, ContentView, AbsoluteLayout, FlexLayout, ContentPresenter and removed legacy-area-perf Startup / Runtime performance area-layout StackLayout, GridLayout, ContentView, AbsoluteLayout, FlexLayout, ContentPresenter labels May 10, 2024
@jsuarezruiz jsuarezruiz added this to the Backlog milestone Jun 6, 2024
@samhouts samhouts removed s/verified Verified / Reproducible Issue ready for Engineering Triage s/triaged Issue has been reviewed labels Jul 3, 2024
@samhouts samhouts added s/verified Verified / Reproducible Issue ready for Engineering Triage s/triaged Issue has been reviewed labels Jul 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-controls-scrollview ScrollView delighter-sc platform/iOS 🍎 s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.)
Projects
None yet
Development

No branches or pull requests

6 participants