dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.24k stars 1.76k forks source link

[Performance] DatePicker/TimePicker first render is too slow #24929

Open OvrBtn opened 1 month ago

OvrBtn commented 1 month ago

Description

I noticed that one of the static pages in my app is rendering fairly slowly but only on the first time.

I've played around with different controls and it turns out that first render of DatePicker and TimePicker is slowing whole navigation.

All the recording below are made in Release configutaion on physical android device - Samsung Galaxy A50.

Here is a recording of page containing only DatePicker:

https://github.com/user-attachments/assets/40534815-22a0-4016-9552-6fee89b4e0b9

With some more UI on the page navigation can become quite bad.

I've recreated same thing inside Maui.Controls.Sample.Sandbox and recorded dotnet trace 2 times maui-app_20240924_232525.speedscope.json In this one I noticed 2 thing: image image

I'm not sure why is the dialog created on control creation at https://github.com/dotnet/maui/blob/68524c8056491da6a40b06dd0c979de1bb40538f/src/Core/src/Handlers/DatePicker/DatePickerHandler.Android.cs#L22-L23 so I removed it completely and it still works fine still since in ShowPickerDialog https://github.com/dotnet/maui/blob/68524c8056491da6a40b06dd0c979de1bb40538f/src/Core/src/Handlers/DatePicker/DatePickerHandler.Android.cs#L137-L140 there already is a null check and dialog is created when needed (I haven't ran any tests though).

Other thing DateTime.ToString in SetText method https://github.com/dotnet/maui/blob/68524c8056491da6a40b06dd0c979de1bb40538f/src/Core/src/Platform/Android/DatePickerExtensions.cs#L56-L59 Replaced it with a constant string "test value" for now to see if anyting else pops out.

maui-app_20240925_153615.speedscope.json dotnet trace after those changes image and it seems like first call of DateTime.Now at https://github.com/dotnet/maui/blob/68524c8056491da6a40b06dd0c979de1bb40538f/src/Controls/src/Core/DatePicker/DatePicker.cs#L18 is taking a lot of time, replaced it with new DateTime() with static values for now.

https://github.com/user-attachments/assets/4a7615a9-dbf7-4bcd-8f27-1a5e1f59379a

And I think it's better. Sooo the thing is that I don't really know what to do with those 2 DateTime methods to make it work fast so I'm reporting what I found here. I hope it's somehow helpful.

In terms of MAUI version, I've forked repo just yesterday (24.09.2024) but the issue has been present for some time. I don't think there was a version where this worked fast.

Steps to Reproduce

  1. Add page containing only DatePicker or TimePicker
  2. Navigate to added page

Link to public reproduction project repository

In this repo Static1 and Static2 buttons. https://github.com/OvrBtn/MAUIPerf

Version with bug

Unknown/Other

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android, I was not able test on other platforms

Affected platform versions

No response

Did you find any workaround?

No response

Relevant log output

No response

drasticactions commented 1 month ago

For the DateTime, I wonder if that's caused by Globalization. Maybe Android/.NET/a combination of the two are loading resources for it on demand, and since that's happening on the UI Thread that causes it to have to wait.

If you set

<InvariantGlobalization>true</InvariantGlobalization>

in your application, that would not invoke any culture settings. Or if you call DateTime.Now.ToString() on startup, it'll invoke whatever libraries are needed for globalization ahead of time.

Zhanglirong-Winnie commented 1 month ago

This issue has been verified using Visual Studio 17.12.0 Preview 2.0(8.0.90 & 8.0.82). Can repro this issue on Android platform.

OvrBtn commented 1 month ago

For the DateTime, I wonder if that's caused by Globalization. Maybe Android/.NET/a combination of the two are loading resources for it on demand, and since that's happening on the UI Thread that causes it to have to wait.

If you set

<InvariantGlobalization>true</InvariantGlobalization>

in your application, that would not invoke any culture settings. Or if you call DateTime.Now.ToString() on startup, it'll invoke whatever libraries are needed for globalization ahead of time.

Hello, you are probably right. I see 2 solutions:

  1. As you proposed: Call DateTime.Now on app startup from worker thread. Although I don't know how threading works in Android/iOS and MAUI - meaning does worker thread receive it's own processor time or is processor time of whole process being divided between UI thread and worker threads? It might be a stupid question but I wouldn't want to additionaly slow down startup of every MAUI app, even when it's not much.

  2. First call of DateTime.Now (and DateTime.ToString) execute on worker thread and then marshall the value back to UI thread. Does it look kind of not elegant? Yes Does it work? Yes The only downside that comes to my mind right now are slower devices and if maybe it could be possible that if executing DateTime methods would take longer than rendering UI then maybe user would see for a moment placeholder date/default date untill worker thread finishes and the date is replaced. Also marshalling between threads has some cost too but it seems like it might be lower than first call of DateTime.Now.

Here is a video of 2. method implemented in MAUI's code: https://github.com/user-attachments/assets/f8db0251-7148-4dfd-a004-5573e4b8dff7

So now the question is would MAUI team accept any of those solutions? Also I'm open to other ideas 😄

OvrBtn commented 1 month ago

Seems like sometimes even on my device the default/placeholder date is visible for a moment. So if there is no other way maybe the best idea would be to combine these 2 solutions?