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

Calling System.TimeZoneInfo.GetSystemTimeZones() is unacceptable slow calling it the first time. #8050

Closed
JeroenBer opened this issue May 18, 2023 · 13 comments · Fixed by dotnet/runtime#88368
Assignees
Labels
Area: Mono Runtime Mono-related issues: BCL bugs, AOT issues, etc.

Comments

@JeroenBer
Copy link

Android application type

.NET Android (net7.0-android, etc.)

Affected platform version

VS 2022 17.5.4

Description

After converting our code from Classic Xamarin to .NET Android we noticed that calling
System.TimeZoneInfo.GetSystemTimeZones() is unacceptable slow the first time. The second time it's fast, probably uses some internal caching.

E.g in fully optimized release mode:
Xamarin: 68ms
.NET Android (.net7.0): 670ms Almost 10 times slower!

In Debug mode the call can even take 4500ms the first time!

This is a problem since we do a lot of timezone conversion also directly after startup and you really notice this hickup from the first call.

Tested it on real device and Emulator Android 13.

Steps to Reproduce

Create a .NET Android app and call:

System.TimeZoneInfo.GetSystemTimeZones()

Notice how slow it is the first time.

Did you find any workaround?

No

Relevant log output

No response

@JeroenBer JeroenBer added Area: App Runtime Issues in `libmonodroid.so`. needs-triage Issues that need to be assigned. labels May 18, 2023
@grendello grendello assigned mdh1418 and unassigned grendello May 18, 2023
@grendello grendello added Area: Mono Runtime Mono-related issues: BCL bugs, AOT issues, etc. and removed Area: App Runtime Issues in `libmonodroid.so`. needs-triage Issues that need to be assigned. labels May 18, 2023
@mdh1418
Copy link
Member

mdh1418 commented Jun 21, 2023

Hi @JeroenBer, when seeing the 4500ms, does that happen on both device and emulator? Could I get the specifications of the real device that the app is being ran on?

Running in Debug on Android 13 emulator I want to try to reproduce the 4500ms timing, but so far, I've ran the .NET Android 7.0.100 workload and traced GetSystemTimeZones first two times at 1200ms, and the last 6 times at 700ms~600ms. I'll start my investigation off of what I'm seeing, but if the 4500ms timing happens to be a different configuration (for example, maybe using Debug with Hot Reload, which would run interpreter instead of JIT compilation) that would be helpful for me to reproduce locally.

Are you grabbing the perf timing using dotnet dsrouter and dotnet trace or something else?

@mdh1418
Copy link
Member

mdh1418 commented Jun 21, 2023

Ignore, double posted by accident

@JeroenBer
Copy link
Author

Thanks for you interest, I will have to re-test and send you the info tomorrow or the day after.

@JeroenBer
Copy link
Author

Hi @mdh1418

I used my own test program to reproduce it, you can find it here:
https://github.com/JeroenBer/TestTimezonePerformance
The solution has a Net7 and a Xamarin project for testing.

I ran some tests again and below are the results I get, for first time I run test after startup. The Debug configuration I just start from VS in Debug mode.

Device Configuration Xamarin AndroidAppNet 7
Simulator Android 13 on AMD Ryzen Threadripper 3960X Debug 450ms 3200ms
Simulator Android 13 on AMD Ryzen Threadripper 3960X Release 700ms 3200ms
Google Pixel 6a Android UpsideDownCake Release 65ms 680ms
Google Pixel 4 Android 13 Release 120ms 1100ms

Notice on real devices in release mode .NET 7 has become 10x slower than Xamarin and it takes a huge time.

I hope this helps ? Just let me know if you need more info.

@tarekgh
Copy link
Member

tarekgh commented Jul 5, 2023

This is a problem since we do a lot of timezone conversion also directly after startup and you really notice this hickup from the first call.

Why are you enumerating all time zones to use for conversion in the first place? Could you please talk more about your scenario to understand what exactly you are doing? Usually, GetSystemTimeZones is costly but apps pay the cost one time. I don't even expect the apps to call this API more than once.

@JeroenBer
Copy link
Author

Yes we have a calendar app so we need to display appointments for different timezones at screen after startup. Since every Android / iOS version has a different list of supported timezones we need to create a mapping table at startup, for mapping application timezones (OS / device independent) to supported device timezones. Of course we only call this once during the application lifetime. Note that calling it a second time it is not slow anymore. It's only slow calling it the first time anyway (GetSystemTimeZones).

But the point is that with the migration from Xamarin to .NET 7 something costly has suddenly become 10x times more costly at runtime and now is a noticeable hickup for users, so we see this as a problem. Have you been able to reproduce it yet ?

@tarekgh
Copy link
Member

tarekgh commented Jul 5, 2023

Since every Android / iOS version has a different list of supported timezones we need to create a mapping table at startup, for mapping application timezones (OS / device independent) to supported device timezones.

Can you please give more details about how do you do the mapping using enumerating all system time zones?

@JeroenBer
Copy link
Author

We have a table with about 250 internal application timezones, we need this since our application runs on different platforms Windows, iOS, Android, MacOS. For each of these 250 timezones there is a list of possible Olson timezone ID's. For each we need to map it to check if it's available on the device, if none are available for a internal application timezone we need to remove the internal application timezones since it's not available on the specific device. If one is available we need to keep it and remember the mapping for further usage. From then on we only use our application timezones.
Basically we build a Dictionary for fast mapping of application timezones to device timezones. For this procedure calling GetSystemTimeZones once is the fastest option, checking each timezone separately is much worse.

Have you been able to reproduce the slow performance ?

@tarekgh
Copy link
Member

tarekgh commented Jul 5, 2023

Have you been able to reproduce the slow performance?

It is expected to have perf cost especially on Linux/Mac based OSs because we must enumerate and open all time zone files on that device in addition doing some more initialization for other stuff related to the enumerated time zones.

In your scenario, do you offer listing all available time zones to the user in the UI? or you are using your mapped dictionary when you need to use it? I am asking because you may delay validating the time zone till it gets used. and at that time you need to validate only one time zone instead of the whole list.

@JeroenBer
Copy link
Author

Well we need it for both so that's not really an option.

I understand it's not lightning fast but how come it's 10x slower then it used to be ?

@mdh1418
Copy link
Member

mdh1418 commented Jul 6, 2023

Hi @JeroenBer, I haven't been able to procure a slow enough device, but I investigated the bottleneck areas for the current implementation. It boils down to GetSystemTimeZones() calling the unix constructor for every single timezone (424 on my emulator), within which will access the timezone database and attempt to populate on the fly the internal fields used as DisplayName, StandardName, and DaylightName values.

I attempted to improve this perf slowdown by lazy initializing these properties in dotnet/runtime#88368, but unfortunately, GetSystemTimeZones() mandates an ordered sort based on DisplayName (which is the most costly of the three and takes ~90% of the constructor's time).

I believe Classic Xamarin was not doing any of that, but populating the information from the timezone files directly.
When you grab all system timezones, do you need the DisplayName? Or would Id/other fields suffice?

@JeroenBer
Copy link
Author

Hi @mdh1418 , thanks for looking into it. In our case we don't need DisplayName, StandardName or DaylightName.

It's a shame they need to be sorted. The official doc doesn't specify on which field they would be sorted: https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.getsystemtimezones?view=net-7.0
Isn't it better to sort on Id ?

@tarekgh
Copy link
Member

tarekgh commented Jul 9, 2023

It's a shame they need to be sorted. The official doc doesn't specify on which field they would be sorted: learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.getsystemtimezones?view=net-7.0

The doc is clearly saying the following:

The collection returned by this method is sorted by UTC offset and, for time zones that have the same UTC offset, by the display name using the current culture. For information about the display name, see [DisplayName](https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.displayname?view=net-7.0).

Isn't it better to sort on Id ?

No, it is not better. Most common scenario using this API is to enumerate the time zones and display the list to the users in the UI. sorting with the display name is correct.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Area: Mono Runtime Mono-related issues: BCL bugs, AOT issues, etc.
Projects
None yet
4 participants