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

[Mono.Android] improve performance of new Java arrays #6870

Merged
merged 2 commits into from Mar 29, 2022

Conversation

jonathanpeppers
Copy link
Member

Context: https://github.com/dotnet/maui/blob/79f2fef9397bbfe2f6aa1527e40f933712a5d2ad/src/Core/src/Platform/Android/ColorStateListExtensions.cs#L50-L51

When running a dotnet new maui app, we noticed the log:

W monodroid: typemap: failed to map managed type to Java type: System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e (Module ID: 8e4cd939-3275-41c4-968d-d5a4376b35f5; Type token: 33554653)
W monodroid-assembly: typemap: called from
W monodroid-assembly: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type )
W monodroid-assembly: at Android.Runtime.JNIEnv.GetJniName(Type )
W monodroid-assembly: at Android.Runtime.JNIEnv.FindClass(Type )
W monodroid-assembly: at Android.Runtime.JNIEnv.NewArray(Array , Type )
W monodroid-assembly: at Android.Runtime.JNIEnv.NewArray[Int32[]](Int32[][] )
W monodroid-assembly: at Android.Content.Res.ColorStateList..ctor(Int32[][] , Int32[] )
W monodroid-assembly: at Microsoft.Maui.Platform.ColorStateListExtensions.CreateButton(Int32 enabled, Int32 disabled, Int32 off, Int32 pressed)

We see this ~17 times on startup. In this case, the Android binding is:

public unsafe ColorStateList (int[][]? states, int[]? colors) : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
{
    //...
    IntPtr native_states = JNIEnv.NewArray (states);

Looking at the stack trace, it seems like this is happening for every
item in the array? We could probably call FindClass (typeof (T))
once, and use it for every item in the array. This would avoid many
typemap lookups.

Previously this showed up in dotnet trace output for a Pixel 5:

6.05ms Mono.Android!Android.Runtime.JNIEnv.NewArray
4.84ms Mono.Android!Android.Runtime.JNIEnv.NewArray(System.Array,System.Type)

After these changes:

5.04ms Mono.Android!Android.Runtime.JNIEnv.NewArray
3.83ms Mono.Android!Android.Runtime.JNIEnv.NewArray(System.Array,System.Type,intptr)

This change seems to save ~1ms on startup for dotnet new maui on a
Pixel 5 device.

@jonathanpeppers jonathanpeppers changed the title [Mono.Android] improve interop of multi-dimensional arrays [Mono.Android] improve performance of multi-dimensional arrays Mar 28, 2022
Context: https://github.com/dotnet/maui/blob/79f2fef9397bbfe2f6aa1527e40f933712a5d2ad/src/Core/src/Platform/Android/ColorStateListExtensions.cs#L50-L51

When running a `dotnet new maui` app, we noticed the log:

    W monodroid: typemap: failed to map managed type to Java type: System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e (Module ID: 8e4cd939-3275-41c4-968d-d5a4376b35f5; Type token: 33554653)
    W monodroid-assembly: typemap: called from
    W monodroid-assembly: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type )
    W monodroid-assembly: at Android.Runtime.JNIEnv.GetJniName(Type )
    W monodroid-assembly: at Android.Runtime.JNIEnv.FindClass(Type )
    W monodroid-assembly: at Android.Runtime.JNIEnv.NewArray(Array , Type )
    W monodroid-assembly: at Android.Runtime.JNIEnv.NewArray[Int32[]](Int32[][] )
    W monodroid-assembly: at Android.Content.Res.ColorStateList..ctor(Int32[][] , Int32[] )
    W monodroid-assembly: at Microsoft.Maui.Platform.ColorStateListExtensions.CreateButton(Int32 enabled, Int32 disabled, Int32 off, Int32 pressed)

We see this ~17 times on startup. In this case, the Android binding is:

    public unsafe ColorStateList (int[][]? states, int[]? colors) : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
    {
        //...
        IntPtr native_states = JNIEnv.NewArray (states);

Looking at the stack trace, it seems like this is happening for every
item in the array? We could probably call `FindClass (typeof (T))`
once, and use it for every item in the array. This would avoid many
typemap lookups.

Previously this showed up in `dotnet trace` output for a Pixel 5:

    6.05ms Mono.Android!Android.Runtime.JNIEnv.NewArray
    4.84ms Mono.Android!Android.Runtime.JNIEnv.NewArray(System.Array,System.Type)

After these changes:

    5.04ms Mono.Android!Android.Runtime.JNIEnv.NewArray
    3.83ms Mono.Android!Android.Runtime.JNIEnv.NewArray(System.Array,System.Type,intptr)

This change seems to save ~1ms on startup for `dotnet new maui` on a
Pixel 5 device.
@jonathanpeppers jonathanpeppers changed the title [Mono.Android] improve performance of multi-dimensional arrays [Mono.Android] improve performance of new Java arrays Mar 29, 2022
@@ -1596,14 +1584,41 @@ public static IntPtr NewArray<T> (T[]? array)
return IntPtr.Zero;

if (typeof (T).IsArray) {
return NewArray (array, typeof (T));
IntPtr grefArrayClass = FindClass (typeof (T));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming: this isn't the "class" of the array; this is the "class" of the element. grefElementClass would be a better name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alas, grefArrayClass is consistent with existing usage in the other NewArray() method, but I was mentally confused the first time I read this… :-(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used grefArrayElementClass, as I found it in another place in this file.

@jonathanpeppers jonathanpeppers marked this pull request as ready for review March 29, 2022 19:28
@jonpryor jonpryor merged commit 0b9a0dc into xamarin:main Mar 29, 2022
@jonathanpeppers jonathanpeppers deleted the MultidimensionalNewArray branch March 29, 2022 19:56
jonathanpeppers added a commit to jonathanpeppers/maui that referenced this pull request Mar 29, 2022
`dotnet trace` output of `dotnet new maui` on a Pixel 5 device shows:

    7.54ms Microsoft.Maui!Microsoft.Maui.Platform.ColorStateListExtensions.CreateDefault
    7.54ms Mono.Android!Android.Content.Res.ColorStateList..ctor(int[][],int[])
    5.04ms Mono.Android!Android.Runtime.JNIEnv.NewArray

Reviewing, the Android API for `ColorStateList`, the C# binding is:

    public unsafe ColorStateList (int[][]? states, int[]? colors)
        : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
    {
        //...
        IntPtr native_states = JNIEnv.NewArray (states);

There is a general performance problem in using C# arrays to call into
Java. We have to create a Java array and copy each array element over
to the Java array. We slightly improved upon this scenario in
xamarin/xamarin-android#6870, but the array duplication remains a
problem.

To solve this:

1. Make the `ColorStateList` ctor a "banned API" in C#.

2. Delete `ColorStates.cs`. Create a private `ColorStates` class in Java.

3. Create Java methods for creating `ColorStateList` objects.

I also removed `ColorExtensions.IsOneColor()`, in favor of the pattern:

    if (PlatformInterop.CreateEditTextColorStateList(platformPicker.TextColors, textColor.ToPlatform()) is ColorStateList c)
        platformPicker.SetTextColor(c);

This reduces the number of JNI calls in this case.

After these changes, we instead get:

    2.50ms Microsoft.Maui!Microsoft.Maui.Platform.ColorStateListExtensions.CreateDefault

Which appears to save ~5ms of startup time on a Pixel 5 in `dotnet new
maui`. Other apps that use `Entry`, `CheckBox`, `Switch`, etc. will
also see an improvement.

The only place I didn't fix this is in `TabPage`, as it was not a
straightforward port. Since most apps will be using Shell going
forward anyways, we can revisit this in the future, if needed.
jonathanpeppers added a commit that referenced this pull request Mar 30, 2022
Context: https://github.com/dotnet/maui/blob/79f2fef9397bbfe2f6aa1527e40f933712a5d2ad/src/Core/src/Platform/Android/ColorStateListExtensions.cs#L50-L51

When running a `dotnet new maui` app, we noticed the log:

	W monodroid: typemap: failed to map managed type to Java type: System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e (Module ID: 8e4cd939-3275-41c4-968d-d5a4376b35f5; Type token: 33554653)
	W monodroid-assembly: typemap: called from
	W monodroid-assembly: at Android.Runtime.JNIEnv.TypemapManagedToJava(Type )
	W monodroid-assembly: at Android.Runtime.JNIEnv.GetJniName(Type )
	W monodroid-assembly: at Android.Runtime.JNIEnv.FindClass(Type )
	W monodroid-assembly: at Android.Runtime.JNIEnv.NewArray(Array , Type )
	W monodroid-assembly: at Android.Runtime.JNIEnv.NewArray[Int32[]](Int32[][] )
	W monodroid-assembly: at Android.Content.Res.ColorStateList..ctor(Int32[][] , Int32[] )
	W monodroid-assembly: at Microsoft.Maui.Platform.ColorStateListExtensions.CreateButton(Int32 enabled, Int32 disabled, Int32 off, Int32 pressed)

We see this ~17 times on startup. In this case, the Android binding is:

	public unsafe ColorStateList (int[][]? states, int[]? colors)
	    : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
	{
	    //...
	    IntPtr native_states = JNIEnv.NewArray (states);

Looking at the stack trace, it seems like this is happening for every
item in the array?  We could probably call `FindClass(typeof (T))`
once, and use it for every item in the array.  This would avoid many
typemap lookups.

Previously this showed up in `dotnet trace` output for a Pixel 5:

	6.05ms Mono.Android!Android.Runtime.JNIEnv.NewArray
	4.84ms Mono.Android!Android.Runtime.JNIEnv.NewArray(System.Array,System.Type)

After these changes:

	5.04ms Mono.Android!Android.Runtime.JNIEnv.NewArray
	3.83ms Mono.Android!Android.Runtime.JNIEnv.NewArray(System.Array,System.Type,intptr)

This change seems to save ~1ms on startup for `dotnet new maui` on a
Pixel 5 device.
jonathanpeppers added a commit to jonathanpeppers/maui that referenced this pull request Jul 5, 2022
`dotnet trace` output of `dotnet new maui` on a Pixel 5 device shows:

    16.63ms microsoft.maui!Microsoft.Maui.Platform.ColorStateListExtensions.CreateDefault(int)
    16.63ms mono.android!Android.Content.Res.ColorStateList..ctor(int[][],int[])
    11.38ms mono.android!Android.Runtime.JNIEnv.NewArray

Reviewing, the Android API for `ColorStateList`, the C# binding is:

    public unsafe ColorStateList (int[][]? states, int[]? colors)
        : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
    {
        //...
        IntPtr native_states = JNIEnv.NewArray (states);

There is a general performance problem in using C# arrays to call into
Java. We have to create a Java array and copy each array element over
to the Java array. We slightly improved upon this scenario in
xamarin/xamarin-android#6870, but the array duplication remains a
problem.

To solve this:

1. Make the `ColorStateList` ctor a "banned API" in C#.

2. Delete `ColorStates.cs`. Create a private `ColorStates` class in Java.

3. Create Java methods for creating `ColorStateList` objects.

I also removed `ColorExtensions.IsOneColor()`, in favor of the pattern:

    if (PlatformInterop.CreateEditTextColorStateList(platformPicker.TextColors, textColor.ToPlatform()) is ColorStateList c)
        platformPicker.SetTextColor(c);

This reduces the number of JNI calls in this case.

After these changes, we instead get:

    2.44ms microsoft.maui!Microsoft.Maui.Platform.ColorStateListExtensions.CreateDefault(int)

Which appears to save ~14ms of startup time on a Pixel 5 in `dotnet new
maui`. Other apps that use `Entry`, `CheckBox`, `Switch`, etc. will
also see an improvement.

The only place I didn't fix this is in `TabPage`, as it was not a
straightforward port. Since most apps will be using Shell going
forward anyways, we can revisit this in the future, if needed.
PureWeen pushed a commit to dotnet/maui that referenced this pull request Jul 18, 2022
`dotnet trace` output of `dotnet new maui` on a Pixel 5 device shows:

    16.63ms microsoft.maui!Microsoft.Maui.Platform.ColorStateListExtensions.CreateDefault(int)
    16.63ms mono.android!Android.Content.Res.ColorStateList..ctor(int[][],int[])
    11.38ms mono.android!Android.Runtime.JNIEnv.NewArray

Reviewing, the Android API for `ColorStateList`, the C# binding is:

    public unsafe ColorStateList (int[][]? states, int[]? colors)
        : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
    {
        //...
        IntPtr native_states = JNIEnv.NewArray (states);

There is a general performance problem in using C# arrays to call into
Java. We have to create a Java array and copy each array element over
to the Java array. We slightly improved upon this scenario in
xamarin/xamarin-android#6870, but the array duplication remains a
problem.

To solve this:

1. Make the `ColorStateList` ctor a "banned API" in C#.

2. Delete `ColorStates.cs`. Create a private `ColorStates` class in Java.

3. Create Java methods for creating `ColorStateList` objects.

I also removed `ColorExtensions.IsOneColor()`, in favor of the pattern:

    if (PlatformInterop.CreateEditTextColorStateList(platformPicker.TextColors, textColor.ToPlatform()) is ColorStateList c)
        platformPicker.SetTextColor(c);

This reduces the number of JNI calls in this case.

After these changes, we instead get:

    2.44ms microsoft.maui!Microsoft.Maui.Platform.ColorStateListExtensions.CreateDefault(int)

Which appears to save ~14ms of startup time on a Pixel 5 in `dotnet new
maui`. Other apps that use `Entry`, `CheckBox`, `Switch`, etc. will
also see an improvement.

The only place I didn't fix this is in `TabPage`, as it was not a
straightforward port. Since most apps will be using Shell going
forward anyways, we can revisit this in the future, if needed.
@github-actions github-actions bot locked and limited conversation to collaborators Jan 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants