Skip to content

Commit

Permalink
Speed up java-to-managed typemap lookups
Browse files Browse the repository at this point in the history
Up until now, Xamarin.Android used string comparison when finding a
Managed type corresponding to a given Java type.  Even though the
strings were pre-sorted at build time, multiple string comparisons
costed more time than necessary.  To improve comparison speed, this
commit implements lookups based on hash values (using the `xxHash`
algorithm) calculated for all the Java names at build time.  This allows
us to process each Java type once at run time - to generate its hash.
After that, the hash is used to binary search an array of hashes and the
result (if found) is an index into array with the appropriate
Java-to-Managed mapping.

This change also allows us to move Java type names from the mapping
structure (`TypeMapJava`) to a separate array.  We used to keep Java
type name in the structure to make matching slightly faster, but it
required unnecessarily complicated structure size calculation at
runtime, so that binary search can properly work on an array of
`TypeMapJava` structures whose size would differ from application to
application (and sometimes even between builds).  The change also saves
space, because when the Java type name was stored in the structure, all
the structures had to have the same size, and thus all type names
shorter than the longest one had to be padded with NUL characters.

A handful of other optimizations are implemented as well.  Namely:

  * the `JNIEnv.RegisterJniNatives` method is now called
    directly (thanks to the `[UnmanagedCallersOnly]` attribute) when
    running under .NET6
  * a conceptually simpler binary search function was implemented, which
    doesn't use C++ templates and also appears to generate faster code.
    There are two versions of the function, one "simple" using the
    standard branching binary search algorithm and the other
    "branchless". The latter is currently not used, needing a better
    timing infrastructure to make sure it's actually faster on Android
    devices (microbenchmarks suggest its faster, application
    measurements when the branchless version is used suggest it's slower
    than the simple one)
  * the `typemap_managed_to_java` and `typemap_java_to_managed` internal
    calls are now registered directly from the `EmbeddedAssemblies`
    class instead of from the `MonodroidRuntime` class
  * a number of native functions are now forcibly inlined
  * a number of native functions are now static instead of instance

Startup performance was measured a .NET6 MAUI application created with
the `dotnet new maui` template and the gains vary depending on where we
look. The `Displayed` time sees changes that are negligible, however the
most affected area of the startup sequence (the call to
`JNIEnv.Initialize`) which registers types and involves the biggest
number of lookups sees improvements of up to 12%.  The measurements have
a degree of uncertainty and instability to them because of our use of
Android `logcat` to report timings as they are taken (`logcat` calls
need to send messages to a system daemon which involves a lot of steps
and allows for a large variation in time spent processing each call) and
also because the `Displayed` time is not a very stable reporting
system (it depends on CPU and GPU load among other factors)

The changes will also positively affect application performance after
startup:

On Pixel 3 XL running Android 12:

| Before | After  | Δ        | Notes                                          |
| ------ | ------ | -------- | ---------------------------------------------- |
| 14.967 | 13.586 | -9.23% ✓ | preload enabled; 32-bit build                  |
| 15.312 | 14.343 | -6.33% ✓ | preload enabled; 32-bit build; no compression  |
| 13.577 | 12.792 | -5.78% ✓ | preload enabled; 64-bit build                  |
| 13.677 | 12.894 | -5.73% ✓ | preload disabled; 64-bit build; no compression |
| 13.601 | 12.838 | -5.61% ✓ | preload disabled; 64-bit build                 |
| 13.656 | 12.953 | -5.15% ✓ | preload enabled; 64-bit build; no compression  |
| 14.638 | 14.070 | -3.88% ✓ | preload disabled; 32-bit build                 |
| 15.053 | 14.526 | -3.50% ✓ | preload disabled; 32-bit build; no compression |

On Pixel 6 XL running Android 12:

| Before | After | Δ         | Notes                                          |
| ------ | ----- | --------- | ---------------------------------------------- |
| 8.972  | 7.826 | -12.78% ✓ | preload enabled; 32-bit build                  |
| 8.833  | 7.823 | -11.43% ✓ | preload enabled; 32-bit build; no compression  |
| 8.611  | 8.031 | -6.74% ✓  | preload disabled; 32-bit build; no compression |
| 6.533  | 6.104 | -6.57% ✓  | preload disabled; 64-bit build; no compression |
| 6.504  | 6.119 | -5.92% ✓  | preload enabled; 64-bit build; no compression  |
| 6.426  | 6.052 | -5.83% ✓  | preload disabled; 64-bit build                 |
| 6.493  | 6.125 | -5.67% ✓  | preload enabled; 64-bit build                  |
| 8.446  | 8.088 | -4.23% ✓  | preload disabled; 32-bit build                 |
  • Loading branch information
grendello committed Apr 12, 2022
1 parent 449a6a0 commit e91a515
Show file tree
Hide file tree
Showing 18 changed files with 548 additions and 368 deletions.
3 changes: 3 additions & 0 deletions src/Mono.Android/Android.Runtime/JNIEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ internal static bool ShouldWrapJavaException (Java.Lang.Throwable? t, [CallerMem
[DllImport ("libc")]
static extern int gettid ();

#if NETCOREAPP
[UnmanagedCallersOnly]
#endif
static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len)
{
string typeName = new string ((char*) typeName_ptr, 0, typeName_len);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public sealed class ApplicationConfig
"map_modules",
"map_module_count",
"java_type_count",
"java_name_width",
"map_java_hashes",
"map_java",
"mono_aot_mode_name",
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
"Size": 3032
},
"assemblies/Java.Interop.dll": {
"Size": 55111
"Size": 55106
},
"assemblies/Mono.Android.dll": {
"Size": 88334
"Size": 88461
},
"assemblies/rc.bin": {
"Size": 1083
Expand All @@ -26,13 +26,13 @@
"Size": 2374
},
"assemblies/UnnamedProject.dll": {
"Size": 3551
"Size": 3546
},
"classes.dex": {
"Size": 345328
},
"lib/arm64-v8a/libmonodroid.so": {
"Size": 380832
"Size": 382304
},
"lib/arm64-v8a/libmonosgen-2.0.so": {
"Size": 3192432
Expand All @@ -47,7 +47,7 @@
"Size": 150032
},
"lib/arm64-v8a/libxamarin-app.so": {
"Size": 8688
"Size": 9424
},
"META-INF/BNDLTOOL.RSA": {
"Size": 1213
Expand All @@ -59,7 +59,7 @@
"Size": 2467
},
"res/drawable-hdpi-v4/icon.png": {
"Size": 4762
"Size": 4791
},
"res/drawable-mdpi-v4/icon.png": {
"Size": 2200
Expand All @@ -83,5 +83,5 @@
"Size": 1904
}
},
"PackageSize": 2701303
"PackageSize": 2705399
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@
"Size": 2604
},
"assemblies/Java.Interop.dll": {
"Size": 67953
"Size": 67956
},
"assemblies/Mono.Android.dll": {
"Size": 256591
"Size": 256630
},
"assemblies/mscorlib.dll": {
"Size": 769016
"Size": 769015
},
"assemblies/System.Core.dll": {
"Size": 28198
"Size": 28199
},
"assemblies/System.dll": {
"Size": 9180
},
"assemblies/UnnamedProject.dll": {
"Size": 2880
"Size": 2881
},
"classes.dex": {
"Size": 347796
Expand All @@ -32,7 +32,7 @@
"Size": 750976
},
"lib/arm64-v8a/libmonodroid.so": {
"Size": 297544
"Size": 296192
},
"lib/arm64-v8a/libmonosgen-2.0.so": {
"Size": 4030448
Expand All @@ -41,7 +41,7 @@
"Size": 65512
},
"lib/arm64-v8a/libxamarin-app.so": {
"Size": 18272
"Size": 19960
},
"META-INF/ANDROIDD.RSA": {
"Size": 1213
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,124 +8,124 @@
"Size": 7247
},
"assemblies/Java.Interop.dll": {
"Size": 62006
"Size": 62017
},
"assemblies/Mono.Android.dll": {
"Size": 441706
"Size": 442008
},
"assemblies/mscorlib.dll": {
"Size": 3798
"Size": 3803
},
"assemblies/netstandard.dll": {
"Size": 5499
"Size": 5503
},
"assemblies/rc.bin": {
"Size": 1083
},
"assemblies/System.Collections.Concurrent.dll": {
"Size": 11227
"Size": 11230
},
"assemblies/System.Collections.dll": {
"Size": 16736
"Size": 16741
},
"assemblies/System.Collections.NonGeneric.dll": {
"Size": 8439
"Size": 8443
},
"assemblies/System.ComponentModel.dll": {
"Size": 1961
"Size": 1965
},
"assemblies/System.ComponentModel.Primitives.dll": {
"Size": 2566
"Size": 2569
},
"assemblies/System.ComponentModel.TypeConverter.dll": {
"Size": 5968
"Size": 5970
},
"assemblies/System.Console.dll": {
"Size": 6530
"Size": 6532
},
"assemblies/System.Core.dll": {
"Size": 1928
"Size": 1932
},
"assemblies/System.Diagnostics.TraceSource.dll": {
"Size": 6756
"Size": 6761
},
"assemblies/System.dll": {
"Size": 2275
"Size": 2279
},
"assemblies/System.Drawing.dll": {
"Size": 1957
"Size": 1961
},
"assemblies/System.Drawing.Primitives.dll": {
"Size": 12199
"Size": 12206
},
"assemblies/System.IO.Compression.dll": {
"Size": 17218
"Size": 17221
},
"assemblies/System.IO.IsolatedStorage.dll": {
"Size": 10566
"Size": 10569
},
"assemblies/System.Linq.dll": {
"Size": 19474
"Size": 19480
},
"assemblies/System.Linq.Expressions.dll": {
"Size": 182081
"Size": 182084
},
"assemblies/System.Net.Http.dll": {
"Size": 65831
"Size": 65842
},
"assemblies/System.Net.Primitives.dll": {
"Size": 22364
"Size": 22368
},
"assemblies/System.Net.Requests.dll": {
"Size": 3731
"Size": 3734
},
"assemblies/System.ObjectModel.dll": {
"Size": 11970
"Size": 11974
},
"assemblies/System.Private.CoreLib.dll": {
"Size": 757425
"Size": 757472
},
"assemblies/System.Private.DataContractSerialization.dll": {
"Size": 191072
"Size": 191079
},
"assemblies/System.Private.Uri.dll": {
"Size": 43677
"Size": 43502
},
"assemblies/System.Private.Xml.dll": {
"Size": 220171
"Size": 220183
},
"assemblies/System.Private.Xml.Linq.dll": {
"Size": 17101
"Size": 17099
},
"assemblies/System.Runtime.CompilerServices.Unsafe.dll": {
"Size": 1214
"Size": 1216
},
"assemblies/System.Runtime.dll": {
"Size": 2557
"Size": 2561
},
"assemblies/System.Runtime.Serialization.dll": {
"Size": 1889
"Size": 1893
},
"assemblies/System.Runtime.Serialization.Formatters.dll": {
"Size": 2634
"Size": 2637
},
"assemblies/System.Runtime.Serialization.Primitives.dll": {
"Size": 3940
"Size": 3943
},
"assemblies/System.Security.Cryptography.Algorithms.dll": {
"Size": 6809
"Size": 6815
},
"assemblies/System.Security.Cryptography.Primitives.dll": {
"Size": 2968
"Size": 2973
},
"assemblies/System.Text.RegularExpressions.dll": {
"Size": 76698
"Size": 76702
},
"assemblies/System.Xml.dll": {
"Size": 1779
"Size": 1782
},
"assemblies/UnnamedProject.dll": {
"Size": 117239
"Size": 117237
},
"assemblies/Xamarin.AndroidX.Activity.dll": {
"Size": 6069
Expand All @@ -134,40 +134,40 @@
"Size": 6095
},
"assemblies/Xamarin.AndroidX.AppCompat.dll": {
"Size": 112590
"Size": 112591
},
"assemblies/Xamarin.AndroidX.CardView.dll": {
"Size": 6809
"Size": 6810
},
"assemblies/Xamarin.AndroidX.CoordinatorLayout.dll": {
"Size": 16603
},
"assemblies/Xamarin.AndroidX.Core.dll": {
"Size": 96723
"Size": 96722
},
"assemblies/Xamarin.AndroidX.DrawerLayout.dll": {
"Size": 14273
"Size": 14271
},
"assemblies/Xamarin.AndroidX.Fragment.dll": {
"Size": 39924
"Size": 39926
},
"assemblies/Xamarin.AndroidX.Legacy.Support.Core.UI.dll": {
"Size": 6132
"Size": 6133
},
"assemblies/Xamarin.AndroidX.Lifecycle.Common.dll": {
"Size": 6592
},
"assemblies/Xamarin.AndroidX.Lifecycle.LiveData.Core.dll": {
"Size": 6671
"Size": 6672
},
"assemblies/Xamarin.AndroidX.Lifecycle.ViewModel.dll": {
"Size": 3273
"Size": 3272
},
"assemblies/Xamarin.AndroidX.Loader.dll": {
"Size": 12671
"Size": 12670
},
"assemblies/Xamarin.AndroidX.RecyclerView.dll": {
"Size": 84688
"Size": 84687
},
"assemblies/Xamarin.AndroidX.SavedState.dll": {
"Size": 5077
Expand All @@ -176,13 +176,13 @@
"Size": 10382
},
"assemblies/Xamarin.AndroidX.ViewPager.dll": {
"Size": 17986
"Size": 17985
},
"assemblies/Xamarin.Forms.Core.dll": {
"Size": 528450
},
"assemblies/Xamarin.Forms.Platform.Android.dll": {
"Size": 384996
"Size": 384997
},
"assemblies/Xamarin.Forms.Platform.dll": {
"Size": 56878
Expand All @@ -191,13 +191,13 @@
"Size": 60774
},
"assemblies/Xamarin.Google.Android.Material.dll": {
"Size": 40134
"Size": 40135
},
"classes.dex": {
"Size": 3458288
},
"lib/arm64-v8a/libmonodroid.so": {
"Size": 382776
"Size": 382304
},
"lib/arm64-v8a/libmonosgen-2.0.so": {
"Size": 3192432
Expand All @@ -212,7 +212,7 @@
"Size": 150032
},
"lib/arm64-v8a/libxamarin-app.so": {
"Size": 133192
"Size": 98624
},
"META-INF/android.support.design_material.version": {
"Size": 12
Expand Down Expand Up @@ -1967,5 +1967,5 @@
"Size": 341228
}
},
"PackageSize": 7930399
"PackageSize": 7942687
}
Loading

0 comments on commit e91a515

Please sign in to comment.