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

Abysmal performance in Interop on iOS #19079

Closed
rolfbjarne opened this issue Sep 21, 2023 · 7 comments · Fixed by #19086
Closed

Abysmal performance in Interop on iOS #19079

rolfbjarne opened this issue Sep 21, 2023 · 7 comments · Fixed by #19086
Labels
performance If an issue or pull request is related to performance
Milestone

Comments

@rolfbjarne
Copy link
Member

From @damiand2 on Wed, 20 Sep 2023 14:59:32 GMT

Description

During tests on iOS for self made binding library that binds to xcframework for google map utils (https://github.com/googlemaps/google-maps-ios-utils) i found that application slows down to a crawl in specific scenario. After instrumenting app (Instruments on macos) i found that that perf drop was related to switch between my code and native one (overriding method on class that binds to native one). After removal of that override, app is fast again.
This kind of interop was also used on Xamarin.Forms (albeit with different binding library and earlier native library version) and there were no problems. Sample screen from Instruments showing dramatic cpu time in specific runtime methods responsible for interop between .net and native code:
image

Steps to Reproduce

  1. clone https://github.com/damiand2/MauiBugiOSInteropPerformance
  2. put your own google map key inside MauiProgram.cs (library deals with google maps, it was the easiest for me to provide quick sample), set up nuget local source to bindings folder inside repo
  3. uncomment method public override bool ShouldRenderAsCluster(ICluster cluster, float zoom) inside ClusterRendererHandler.cs
  4. run app on simulator or real iphone, does not matter
  5. Map will take you to Czech country, library uses clustering of pins, double click anywhere to zoom in and therefore break down clusters into smaller ones, repeat this untill there are no more cluster and you only get singluar pins.
  6. try to move map left and right, notice how sluggish it becomes (UI hangs for almost second sometimes)
  7. Stop app, comment back that override from 3rd point, restart app
  8. repeat steps from 5th point, notice that now map scrolling is very smooth

Link to public reproduction project repository

https://github.com/damiand2/MauiBugiOSInteropPerformance

Version with bug

7.0.92

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

Affected platform versions

No response

Did you find any workaround?

No response

Relevant log output

No response

Copied from original issue dotnet/maui#17516

@rolfbjarne
Copy link
Member Author

From @drasticactions on Thu, 21 Sep 2023 03:10:26 GMT

This doesn't seem to be a MAUI UI issue, but a xamarin-macios one or maybe a runtime issue?

@rolfbjarne What are your thoughts on this?

@rolfbjarne
Copy link
Member Author

From @rolfbjarne on Thu, 21 Sep 2023 07:13:19 GMT

I tried your project, but it seems you need to check in the nupkg too:

error : Could not find file '/Users/rolf/test/bugs/MauiBugiOSInteropPerformance/bindings/maputilsbinding/1.0.1/maputilsbinding.1.0.1.nupkg'.

I have a suggestion though to try to improve performance: disable the interpreter and use the static registrar:

<PropertyGroup>
    <UseInterpreter>false</UseInterpreter>
    <Registrar>static</Registrar>
</PropertyGroup>

that should speed up execution.

@rolfbjarne
Copy link
Member Author

From @damiand2 on Thu, 21 Sep 2023 07:35:36 GMT

@rolfbjarne - sorry for nupkg, you can try again, file is uploaded now.
As for PropertyGroup - did not seem to help.

@rolfbjarne
Copy link
Member Author

From @rolfbjarne on Thu, 21 Sep 2023 09:59:30 GMT

I can reproduce this, moving to the correct repo.

@rolfbjarne rolfbjarne added the performance If an issue or pull request is related to performance label Sep 21, 2023
@rolfbjarne rolfbjarne added this to the Future milestone Sep 21, 2023
rolfbjarne added a commit to rolfbjarne/xamarin-macios that referenced this issue Sep 21, 2023
…ixes xamarin#19079.

Cache:

* Assembly -> assembly name, because Assembly.GetName ().Name is quite slow.
* Token reference -> member lookup, because it takes a non-significant amount
  of time to do the lookup each time.

Fixes xamarin#19079.
@rolfbjarne
Copy link
Member Author

I've implemented a few fixes that should help this scenario, we'll try to get them in .NET 8.

In any case, here's a workaround that seems to completely solve the problem: https://gist.github.com/rolfbjarne/3feeee9ba3da58f9fca3a6976b96a4c1

@damiand2
Copy link

thanks @rolfbjarne . Please let me know when you will know if perf fixes will be in .net 8 GA - and therefore if those manual calls to objc_msgSend will be needed

rolfbjarne added a commit that referenced this issue Oct 12, 2023
…ixes #19079. (#19086)

Cache:

* Assembly -> assembly name, because Assembly.GetName ().Name is quite slow.
* Token reference -> member lookup, because it takes a non-significant amount
  of time to do the lookup each time.

Assembly.GetName ().Name used to be 75% of the time in Runtime.GetINativeObject_Static:

![Screenshot 2023-09-21 at 17 55 07](https://github.com/xamarin/xamarin-macios/assets/249268/87d456a6-9aa6-4a00-8631-d125eb4b70c2)

Text table:

```
|   6.82 s  | 100.0% |     0 s    |  MauiBugiOSInteropPerformance (17191)
|   6.82 s  | 100.0% |     0 s    |   Main Thread  0x4a830e
|   6.82 s  | 100.0% |  1.00 ms   |    xamarin_get_inative_object_static
|   6.80 s  |  99.8% |     0 s    |     wrapper_native_to_managed_ObjCRuntime_Runtime_get_inative_object_static_intptr_sbyte_uint_uint_intptr_
|   6.79 s  |  99.6% |  2.00 ms   |      ObjCRuntime_Runtime_get_inative_object_static_intptr_sbyte_uint_uint_intptr_
|   6.79 s  |  99.5% |  3.00 ms   |       ObjCRuntime_Runtime_GetINativeObject_Static_intptr_sbyte_uint_uint
|   4.23 s  |  62.0% |  4.00 ms   |        ObjCRuntime_Class_ResolveTypeTokenReference_uint
|   4.23 s  |  62.0% |  8.00 ms   |         ObjCRuntime_Class_ResolveTokenReference_uint_uint
|   4.07 s  |  59.7% |     0 s    |          ObjCRuntime_Class_ResolveAssembly_intptr
|   4.07 s  |  59.7% | 25.00 ms   |           ObjCRuntime_Class_TryResolveAssembly_intptr_System_Reflection_Assembly_
|   3.35 s  |  49.2% | 22.00 ms   |            System_Reflection_Assembly_GetName
| 668.00 ms |   9.8% |     0 s    |            System_AppDomain_GetAssemblies
|  25.00 ms |   0.3% | 25.00 ms   |            ObjCRuntime_Runtime_StringEquals_intptr_string
| 107.00 ms |   1.5% |  5.00 ms   |          ObjCRuntime_Class_ResolveModule_System_Reflection_Assembly_uint
|  40.00 ms |   0.5% |     0 s    |          ObjCRuntime_Class_ResolveToken_System_Reflection_Module_uint
|   1.00 ms |   0.0% |  1.00 ms   |          plt_ObjCRuntime_Class_ResolveToken_System_Reflection_Module_uint
|   2.54 s  |  37.3% |  3.00 ms   |        ObjCRuntime_Runtime_GetINativeObject_intptr_bool_System_Type_System_Type
|   2.33 s  |  34.2% |  1.00 ms   |         ObjCRuntime_Runtime_LookupINativeObjectImplementation_intptr_System_Type_System_Type_bool
|   2.32 s  |  33.9% |  1.00 ms   |          ObjCRuntime_Runtime_FindProtocolWrapperType_System_Type
|   2.12 s  |  31.0% |  1.00 ms   |           ObjCRuntime_Class_ResolveTypeTokenReference_uint
|   2.12 s  |  31.0% |  7.00 ms   |            ObjCRuntime_Class_ResolveTokenReference_uint_uint
|   2.04 s  |  29.9% |  1.00 ms   |             ObjCRuntime_Class_ResolveAssembly_intptr
|   2.04 s  |  29.9% | 13.00 ms   |              ObjCRuntime_Class_TryResolveAssembly_intptr_System_Reflection_Assembly_
|   1.69 s  |  24.8% | 19.00 ms   |               System_Reflection_Assembly_GetName
|   1.67 s  |  24.5% |  9.00 ms   |                System_Reflection_RuntimeAssembly_GetName_bool
| 872.00 ms |  12.7% | 16.00 ms   |                 System_Reflection_AssemblyName_Create_intptr_string
| 791.00 ms |  11.6% |  1.00 ms   |                 System_Reflection_RuntimeAssembly_GetInfo_System_Reflection_RuntimeAssembly_AssemblyInfoKind
| 319.00 ms |   4.6% |  1.00 ms   |               System_AppDomain_GetAssemblies
|  14.00 ms |   0.2% | 14.00 ms   |               ObjCRuntime_Runtime_StringEquals_intptr_string
|   4.00 ms |   0.0% |  4.00 ms   |               plt_ObjCRuntime_Runtime_StringEquals_intptr_string
|  54.00 ms |   0.7% |     0 s    |             ObjCRuntime_Class_ResolveModule_System_Reflection_Assembly_uint
|  15.00 ms |   0.2% |  1.00 ms   |             ObjCRuntime_Class_ResolveToken_System_Reflection_Module_uint

```

and Class.ResolveTokenReference was 90% of the time in Runtime.GetINativeObject_Static:

![Screenshot 2023-09-21 at 17 55 40](https://github.com/xamarin/xamarin-macios/assets/249268/75adc9bf-ca5b-4bc6-96c6-ace4cb39a0d4)

```
|   6.82 s    |  100.0%   |	   0 s     | MauiBugiOSInteropPerformance (17191)
|   6.82 s    |  100.0%   |	   0 s     |  Main Thread  0x4a830e
|   6.82 s    |  100.0%   |	1.00 ms    |   xamarin_get_inative_object_static
|   6.80 s    |   99.8%   |	   0 s     |    wrapper_native_to_managed_ObjCRuntime_Runtime_get_inative_object_static_intptr_sbyte_uint_uint_intptr_
|   6.79 s    |   99.6%   |	2.00 ms    |     ObjCRuntime_Runtime_get_inative_object_static_intptr_sbyte_uint_uint_intptr_
|   6.79 s    |   99.5%   |	3.00 ms    |      ObjCRuntime_Runtime_GetINativeObject_Static_intptr_sbyte_uint_uint
|   4.23 s    |   62.0%   |	4.00 ms    |       ObjCRuntime_Class_ResolveTypeTokenReference_uint
|   4.23 s    |   62.0%   |	8.00 ms    |        ObjCRuntime_Class_ResolveTokenReference_uint_uint
|   2.54 s    |   37.3%   |	3.00 ms    |       ObjCRuntime_Runtime_GetINativeObject_intptr_bool_System_Type_System_Type
|   2.33 s    |   34.2%   |	1.00 ms    |        ObjCRuntime_Runtime_LookupINativeObjectImplementation_intptr_System_Type_System_Type_bool
|   2.32 s    |   33.9%   |	1.00 ms    |         ObjCRuntime_Runtime_FindProtocolWrapperType_System_Type
|   2.12 s    |   31.0%   |	1.00 ms    |          ObjCRuntime_Class_ResolveTypeTokenReference_uint
|   2.12 s    |   31.0%   |	7.00 ms    |           ObjCRuntime_Class_ResolveTokenReference_uint_uint
| 181.00 ms   |    2.6%   |	4.00 ms    |          ObjCRuntime_Class_GetTokenReference_System_Type_bool
|  11.00 ms   |    0.1%   |	   0 s     |          wrapper_managed_to_native_ObjCRuntime_Runtime_xamarin_find_protocol_wrapper_type_uint
|   3.00 ms   |    0.0%   |	   0 s     |          System_Type_get_IsInterface
|   1.00 ms   |    0.0%   |	1.00 ms    |          plt_System_Type_get_IsByRef
|   1.00 ms   |    0.0%   |	1.00 ms    |          System_Type_get_IsByRef
|  10.00 ms   |    0.1%   |	   0 s     |         System_RuntimeType_IsAssignableFrom_System_Type
|   5.00 ms   |    0.0%   |	   0 s     |         System_Type_get_IsInterface
|   2.00 ms   |    0.0%   |	   0 s     |         System_Type_get_IsByRef
| 192.00 ms   |    2.8%   |	3.00 ms    |        ObjCRuntime_Runtime_ConstructINativeObject_T_REF_intptr_bool_System_Type_ObjCRuntime_Runtime_MissingCtorResolution
|   8.00 ms   |    0.1%   |	1.00 ms    |        ObjCRuntime_Runtime_TryGetNSObject_intptr_bool
|   4.00 ms   |    0.0%   |	1.00 ms    |        System_RuntimeType_IsSubclassOf_System_Type
|   1.00 ms   |    0.0%   |	1.00 ms    |        mono_monitor_enter_v4_fast
|   8.00 ms   |    0.1%   |	3.00 ms    |       ObjCRuntime_Runtime_AllocGCHandle_object_System_Runtime_InteropServices_GCHandleType
|   1.00 ms   |    0.0%   |	1.00 ms    |       plt_ObjCRuntime_Runtime_AllocGCHandle_object_System_Runtime_InteropServices_GCHandleType
|   9.00 ms   |    0.1%   |	   0 s     |     mono_threads_detach_coop
|   5.00 ms   |    0.0%   |	   0 s     |     mono_threads_attach_coop
|  12.00 ms   |    0.1%   |	1.00 ms    |    xamarin_gchandle_unwrap
```

There's some overlap here - the Assembly.GetName ().Name cache would be redundant if it was only called from Class.ResolveTokenReference - but since it's called in more places (it still shows up in profiles once Class.ResolveTokenReference is cached), I kept both caches.

Fixes #19079.

---------

Co-authored-by: Manuel de la Pena <mandel@microsoft.com>
@rolfbjarne
Copy link
Member Author

Please let me know when you will know if perf fixes will be in .net 8 GA

No, these perf fixes will not be in .NET 8 GA, it's a bit too late and risky at this point. We'll try to get them into a .NET 8 service release though.

@rolfbjarne rolfbjarne modified the milestones: Future, .NET 9 Oct 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance If an issue or pull request is related to performance
Projects
None yet
2 participants