-
Notifications
You must be signed in to change notification settings - Fork 565
Startup performance improvements (less reflection) #4302
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
Conversation
3f309de to
7d6a3e4
Compare
|
/azp run |
|
Pull request contains merge conflicts. |
7d6a3e4 to
f46177a
Compare
radekdoulik
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The runtime init improvements are nice! Do you know what might be slowing the Activity Displayed times on 64bit?
|
@radekdoulik I think |
|
It was quite reliable for me so far. It would be interesting to compare the Activity Displayed times with video recording to see how much does it correlate to the wall clock. |
|
it appears to be heavily influenced by what the device/emulator is doing at the time. From what I saw in a quick test the difference of time between the end of |
fd9e623 to
d34ac92
Compare
| internal AndroidRuntime (IntPtr jnienv, IntPtr vm, bool allocNewObjectSupported, IntPtr classLoader, IntPtr classLoader_loadClass) | ||
| : base (new AndroidRuntimeOptions (jnienv, vm, allocNewObjectSupported, classLoader, classLoader_loadClass)) | ||
| internal AndroidRuntime (IntPtr jnienv, IntPtr vm, bool allocNewObjectSupported, IntPtr classLoader, IntPtr classLoader_loadClass, | ||
| bool jniAddNativeMethodRegistrationAttributePresent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, let's not align parameters to (. (I know Emacs likes that. Emacs is wrong. ;-)
Rationale: If (when?) this type is renamed, it means that either all of these will be mis-indented, or they'll need to be re-indented so that things look proper.
Instead, it should be indented two tabstops, and/or all parameters should be done on a newline, each one being two tabstops:
internal AndroidRuntime (IntPtr jnienv,
IntPtr vm,
bool allocNewObjectSupported,
IntPtr classLoader,
IntPtr classLoader_loadClass,
bool jniAddNativeMethodRegistrationAttributePresent)
{
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Emacs isn't wrong, but I don't care enough to defend the position :) Changes applied (in 2 more places in addition to this)
| GC.SuppressFinalize (obj); | ||
| } | ||
|
|
||
| internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPtr, IntPtr javaExceptionPtr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it would make more sense to just leave this entirely in Java & C...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should absolutely move all this stuff into XamarinUncaughtExceptionHandler.
Here's my conceptual problem: Thread.getDefaultUncaughtExceptionHandler() forms a "linked list", and it is the responsibility of all Thread.UncaughtExceptionHandler implementations to propagate the exception to their "parent" handler within Thread.UncaughtExceptionHandler.uncaughtException().
The problem here is twofold:
UncaughtExceptionHandlerinstantiation is "responsible" for callingThread.DefaultUncaughtExceptionHandlerand obtaining the "parent" handler, andUncaughtExceptionHandleris only instantiated when there's an unhandled exception, so it's not "clean" as of process startup, and- Propagating the unhandled exception is thus poorly defined, plus
- It's way confusion trying to keep these four different spots in mind and in sync (
XamarinUncaughtExceptionHandler,UncaughtExceptionHandler,JNIEnv.PropagateUncaughtException(),UncaughtExceptionHandler.UncaughtException()).
It does not give me a good feeling.
Instead, let's "move" the "default uncaught exception handler linked list management" of UncaughtExceptionHandler into XamarinUncaughtExceptionHandler, and then move UncaughtExceptionHandler.UncaughtException() into JNIEnv.PropagateUncaughtException().
This should kill two places to try to keep track of things, simplifying logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, changes applied. Working on this I wanted to make the minimum amount of changes to minimize potential of breaking something that's been in place for years, but I agree with your assessment 100%
d34ac92 to
5fe8523
Compare
|
I was thinking about the times here and my guess is that because we are postponing the uncaught handler, we are not doing any jni type registration in runtime init anymore. Thus all the JITing of the registration code is delayed between runtime init and activity displayed? That would explain the large gain in runtime init and small difference around activity displayed. This guess might be confirmed by measuring jit times if needed (https://github.com/xamarin/xamarin-android/blob/master/Documentation/guides/profiling.md#profiling-the-jit-compiler) |
|
@radekdoulik yes, less JIT time plays a large role here, another is JNI invocations (much less impact than JIT, though). It is possible that some of the JIT load shifted to the "end" of startup, in this case to the rendering phase. |
| if (!tmg.Generate (types, TypemapOutputDirectory, GenerateNativeAssembly, out ApplicationConfigTaskState appConfState)) | ||
| throw new XamarinAndroidException (4308, Properties.Resources.XA4308); | ||
| GeneratedBinaryTypeMaps = tmg.GeneratedBinaryTypeMaps.ToArray (); | ||
| BuildEngine4.RegisterTaskObject (ApplicationConfigTaskState.RegisterTaskObjectKey, appConfState, RegisteredTaskObjectLifetime.AppDomain, allowEarlyCollection: false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@grendello this is going to put this object in memory until the IDE is closed (and will keep it between projects and builds). Is that your intent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, no, not really. I thought msbuild goes away after the build is done?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RegisteredTaskObjectLifetime.AppDomain will keep the object in the app domain (the IDE)
RegisteredTaskObjectLifetime.Build will keep it for the duration of the build only. So I guess you want the latter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RegisteredTaskObjectLifetime.AppDomain will keep the object in the app domain (the IDE)
RegisteredTaskObjectLifetime.Build will keep it for the duration of the build only. So I guess you want the latter?
| if (!tmg.Generate (types, TypemapOutputDirectory, GenerateNativeAssembly, out ApplicationConfigTaskState appConfState)) | ||
| throw new XamarinAndroidException (4308, Properties.Resources.XA4308); | ||
| GeneratedBinaryTypeMaps = tmg.GeneratedBinaryTypeMaps.ToArray (); | ||
| BuildEngine4.RegisterTaskObject (ApplicationConfigTaskState.RegisterTaskObjectKey, appConfState, RegisteredTaskObjectLifetime.AppDomain, allowEarlyCollection: false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@grendello this is going to put this object in memory until the IDE is closed (and will keep it between projects and builds). Is that your intent?
6c44310 to
b0da9a7
Compare
|
Hmm, the tests failed on jenkins again, however locally they worked fine: |
Context: dotnet/android#4302 Context: #582 Java.Interop tests use their own runtime which doesn't know about the results of Xamarin.Android's build process scanning for the presence of the `[JniAddNativeMethodRegistration]` custom attribute. In addition, Java.Interop's Java type scanner (for typemaps) does not include in its results the types in `Java.Interop-Tests` which derive from `Java.Interop.JavaObject` (which is *functionally* equivalent to Xamarin.Android's `Java.Lang.Object`) and, thus, the scan doesn't notice that the custom attribute mentioned above is used by a few test classes. In effect, two tests fail with a similar exception: Java.Lang.LinkageError : No implementation found for void com.xamarin.interop.CallVirtualFromConstructorDerived.calledFromConstructor(int) (tried Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor and Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor__I) at Java.Interop.JniEnvironment+InstanceMethods.CallNonvirtualVoidMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x0008e] in <d30c11568ddc48d5aff8f2d4b2eaa867>:0 at Java.Interop.GenericMarshaler.JniPeerInstanceMethodsExtensions._InvokeConstructor[T] (Java.Interop.JniPeerMembers+JniInstanceMethods peer, System.String constructorSignature, Java.Interop.IJavaPeerable self, T value) [0x00059] in <f6f553d46eb149a29e6542964ef27aaa>:0 at Java.Interop.GenericMarshaler.JniPeerInstanceMethodsExtensions.FinishGenericCreateInstance[T] (Java.Interop.JniPeerMembers+JniInstanceMethods peer, System.String constructorSignature, Java.Interop.IJavaPeerable self, T value) [0x00029] in <f6f553d46eb149a29e6542964ef27aaa>:0 at Java.InteropTests.CallVirtualFromConstructorBase..ctor (System.Int32 value) [0x0004e] in <d39267c627f54df3bc31406b36effb6c>:0 at Java.InteropTests.CallVirtualFromConstructorDerived..ctor (System.Int32 value) [0x00000] in <d39267c627f54df3bc31406b36effb6c>:0 at Java.InteropTests.InvokeVirtualFromConstructorTests.InvokeVirtualFromConstructor () [0x00000] in <d39267c627f54df3bc31406b36effb6c>:0 at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&) at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <26b1d17e27e54492bd3e4105b16bfc7e>:0 --- End of managed Java.Lang.LinkageError stack trace --- java.lang.UnsatisfiedLinkError: No implementation found for void com.xamarin.interop.CallVirtualFromConstructorDerived.calledFromConstructor(int) (tried Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor and Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor__I) at com.xamarin.interop.CallVirtualFromConstructorDerived.calledFromConstructor(Native Method) at com.xamarin.interop.CallVirtualFromConstructorBase.<init>(Unknown Source:35) at com.xamarin.interop.CallVirtualFromConstructorDerived.<init>(Unknown Source:2) at crc64f295cabbf85394f5.TestSuiteInstrumentation.n_onStart(Native Method) at crc64f295cabbf85394f5.TestSuiteInstrumentation.onStart(Unknown Source:0) at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189) Force the tests to always assume the custom attribute is present, thus restoring the default behavior from before b33d183.
Changes: dotnet/java-interop@7582716...27cfd45 Context: dotnet#4302 * dotnet/java-interop@27cfd45: [TestJVM] Force looking for the [JniAddNativeMethodRegistration] (dotnet#595)
Changes: dotnet/java-interop@7582716...27cfd45 Context: #4302 * dotnet/java-interop@27cfd45: [TestJVM] Force looking for the [JniAddNativeMethodRegistration] (#595)
Context: dotnet/android#4302 Context: #582 Java.Interop tests use their own runtime which doesn't know about the results of Xamarin.Android's build process scanning for the presence of the `[JniAddNativeMethodRegistration]` custom attribute. In addition, Java.Interop's Java type scanner (for typemaps) does not include in its results the types in `Java.Interop-Tests` which derive from `Java.Interop.JavaObject` (which is *functionally* equivalent to Xamarin.Android's `Java.Lang.Object`) and, thus, the scan doesn't notice that the custom attribute mentioned above is used by a few test classes. In effect, two tests fail with a similar exception: Java.Lang.LinkageError : No implementation found for void com.xamarin.interop.CallVirtualFromConstructorDerived.calledFromConstructor(int) (tried Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor and Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor__I) at Java.Interop.JniEnvironment+InstanceMethods.CallNonvirtualVoidMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x0008e] in <d30c11568ddc48d5aff8f2d4b2eaa867>:0 at Java.Interop.GenericMarshaler.JniPeerInstanceMethodsExtensions._InvokeConstructor[T] (Java.Interop.JniPeerMembers+JniInstanceMethods peer, System.String constructorSignature, Java.Interop.IJavaPeerable self, T value) [0x00059] in <f6f553d46eb149a29e6542964ef27aaa>:0 at Java.Interop.GenericMarshaler.JniPeerInstanceMethodsExtensions.FinishGenericCreateInstance[T] (Java.Interop.JniPeerMembers+JniInstanceMethods peer, System.String constructorSignature, Java.Interop.IJavaPeerable self, T value) [0x00029] in <f6f553d46eb149a29e6542964ef27aaa>:0 at Java.InteropTests.CallVirtualFromConstructorBase..ctor (System.Int32 value) [0x0004e] in <d39267c627f54df3bc31406b36effb6c>:0 at Java.InteropTests.CallVirtualFromConstructorDerived..ctor (System.Int32 value) [0x00000] in <d39267c627f54df3bc31406b36effb6c>:0 at Java.InteropTests.InvokeVirtualFromConstructorTests.InvokeVirtualFromConstructor () [0x00000] in <d39267c627f54df3bc31406b36effb6c>:0 at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&) at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <26b1d17e27e54492bd3e4105b16bfc7e>:0 --- End of managed Java.Lang.LinkageError stack trace --- java.lang.UnsatisfiedLinkError: No implementation found for void com.xamarin.interop.CallVirtualFromConstructorDerived.calledFromConstructor(int) (tried Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor and Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor__I) at com.xamarin.interop.CallVirtualFromConstructorDerived.calledFromConstructor(Native Method) at com.xamarin.interop.CallVirtualFromConstructorBase.<init>(Unknown Source:35) at com.xamarin.interop.CallVirtualFromConstructorDerived.<init>(Unknown Source:2) at crc64f295cabbf85394f5.TestSuiteInstrumentation.n_onStart(Native Method) at crc64f295cabbf85394f5.TestSuiteInstrumentation.onStart(Unknown Source:0) at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189) Force the tests to always assume the custom attribute is present, thus restoring the default behavior from before b33d183.
Changes: dotnet/java-interop@9324e1e...35f30bf Context: #4302 * dotnet/java-interop@35f30bf4: [TestJVM] Force looking for the [JniAddNativeMethodRegistration] (#595)
src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigTaskState.cs
Outdated
Show resolved
Hide resolved
c82f7d7 to
4b8a050
Compare
|
There's a linker failure in a couple of tests: However, this is caused by the wrong I am unable to reproduce the error locally (tried both with XA |
|
So it looks like we are hitting this following code https://github.com/microsoft/msbuild/blame/e5218a90cf743c8bd5f489c36e72643d8a8c6c61/src/Tasks/Microsoft.CSharp.CurrentVersion.targets#L372. Which is auto including mscorlib from the system .net. Interestingly |
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Java.Interop uses the `JniAddNativeMethodRegistrationAttribute` custom attribute to determine whether to register native methods for a given type. It turns out that in most cases there isn't a single instance of this attribute throughout the application and all the calls to `GetCustomAttribute` end up returning nothing while incurring a large performance penalty on application startup. Eliminate the call after checking that no type in the application is decorated with the above custom attribute. Another observation made while profiling a couple of XA applications was that the installation of the uncaught exception handler with `Java.Lang.Thread` was quite expensive while, in most cases, completely not useful during startup (and, hopefully, during application lifetime). Instead of registering a managed type to handle the exceptions, register the handler in Java which then calls down to Xamarin.Android native runtime which, in turn, propagates the exception to the managed code. Gains in startup time are quite nice: Device name: **Pixel 3 XL** Device architecture: **arm64-v8a** Number of test runs: **10** Test application: **Xamarin.Forms integration test** | | **Native to managed** | **Runtime init** | **Displayed** | **Notes** | |-----------------|------------------------|------------------|---------------|--------------------------------| | **master** | 131.278 | 149.074 | 789.10 | preload enabled; 32-bit build | | **this commit** | 49.446 | 66.989 | 764.30 | | | **master** | 132.315 | 147.187 | 795.60 | preload disabled; 32-bit build | | **this commit** | 48.876 | 63.822 | 754.30 | | | **master** | 121.544 | 137.736 | 728.20 | preload enabled; 64-bit build | | **this commit** | 45.350 | 61.464 | 699.50 | | | **master** | 123.448 | 137.052 | 727.40 | preload disabled; 64-bit build | | **this commit** | 44.765 | 58.047 | 689.00 | | Device name: **Pixel 3 XL** Device architecture: **arm64-v8a** Number of test runs: **10** Test application: Xamarin.Forms "Hello World" app with one label | | **Native to managed** | **Runtime init** | **Displayed** | **Notes** | |-----------------|------------------------|------------------|---------------|--------------------------------| | **master** | 122.090 | 142.004 | 639.00 | preload enabled; 32-bit build | | **this commit** | 44.370 | 63.803 | 586.10 | | | **master** | 121.110 | 134.378 | 634.20 | preload disabled; 32-bit build | | **this commit** | 45.085 | 57.992 | 580.40 | | | **master** | 120.973 | 141.235 | 637.20 | preload enabled; 64-bit build | | **this commit** | 44.767 | 63.846 | 578.50 | | | **master** | 120.785 | 134.588 | 627.00 | preload disabled; 64-bit build | | **this commit** | 44.859 | 57.590 | 575.40 | |
c9b4ee5 to
4e526db
Compare
Context: dotnet/android#4302 Context: #582 Java.Interop tests use their own runtime which doesn't know about the results of Xamarin.Android's build process scanning for the presence of the `[JniAddNativeMethodRegistration]` custom attribute. In addition, Java.Interop's Java type scanner (for typemaps) does not include in its results the types in `Java.Interop-Tests` which derive from `Java.Interop.JavaObject` (which is *functionally* equivalent to Xamarin.Android's `Java.Lang.Object`) and, thus, the scan doesn't notice that the custom attribute mentioned above is used by a few test classes. In effect, two tests fail with a similar exception: Java.Lang.LinkageError : No implementation found for void com.xamarin.interop.CallVirtualFromConstructorDerived.calledFromConstructor(int) (tried Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor and Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor__I) at Java.Interop.JniEnvironment+InstanceMethods.CallNonvirtualVoidMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x0008e] in <d30c11568ddc48d5aff8f2d4b2eaa867>:0 at Java.Interop.GenericMarshaler.JniPeerInstanceMethodsExtensions._InvokeConstructor[T] (Java.Interop.JniPeerMembers+JniInstanceMethods peer, System.String constructorSignature, Java.Interop.IJavaPeerable self, T value) [0x00059] in <f6f553d46eb149a29e6542964ef27aaa>:0 at Java.Interop.GenericMarshaler.JniPeerInstanceMethodsExtensions.FinishGenericCreateInstance[T] (Java.Interop.JniPeerMembers+JniInstanceMethods peer, System.String constructorSignature, Java.Interop.IJavaPeerable self, T value) [0x00029] in <f6f553d46eb149a29e6542964ef27aaa>:0 at Java.InteropTests.CallVirtualFromConstructorBase..ctor (System.Int32 value) [0x0004e] in <d39267c627f54df3bc31406b36effb6c>:0 at Java.InteropTests.CallVirtualFromConstructorDerived..ctor (System.Int32 value) [0x00000] in <d39267c627f54df3bc31406b36effb6c>:0 at Java.InteropTests.InvokeVirtualFromConstructorTests.InvokeVirtualFromConstructor () [0x00000] in <d39267c627f54df3bc31406b36effb6c>:0 at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&) at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <26b1d17e27e54492bd3e4105b16bfc7e>:0 --- End of managed Java.Lang.LinkageError stack trace --- java.lang.UnsatisfiedLinkError: No implementation found for void com.xamarin.interop.CallVirtualFromConstructorDerived.calledFromConstructor(int) (tried Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor and Java_com_xamarin_interop_CallVirtualFromConstructorDerived_calledFromConstructor__I) at com.xamarin.interop.CallVirtualFromConstructorDerived.calledFromConstructor(Native Method) at com.xamarin.interop.CallVirtualFromConstructorBase.<init>(Unknown Source:35) at com.xamarin.interop.CallVirtualFromConstructorDerived.<init>(Unknown Source:2) at crc64f295cabbf85394f5.TestSuiteInstrumentation.n_onStart(Native Method) at crc64f295cabbf85394f5.TestSuiteInstrumentation.onStart(Unknown Source:0) at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189) Force the tests to always assume the custom attribute is present, thus restoring the default behavior from before b33d183.
Reduce or remove reflection use around JNI method registration, and rework how uncaught exceptions are handled. Java.Interop provides an alternate mechanism to allow registering Java native methods: [the `[JniAddNativeMethodRegistrationAttribute]` custom attribute][0] which can be placed on methods of a type to allow "manual" insertion into the [`JNIEnv::RegisterNatives()`][2] process. The `Java.Interop-Tests.dll` unit test assembly (130905e) uses this alternate native method registration mechanism.. Unfortunately, `[JniAddNativeMethodRegistration]` is looked up via Reflection, which isn't exactly "fast". Given that `[JniAddNativeMethodRegistration]` is only used by one assembly *in the world*, let's optimize the common case: [dotnet/java-interop@b33d183d][3] made the `[JniAddNativeMethodRegistration]` lookup *optional*. Disable the custom attribute lookup *by default*, and allow it to be enabled by setting the `$(_SkipJniAddNativeMethodRegistrationAttributeScan)` MSBuild property to True. Yes, this property is "private". We'll investigate more automatic and optimized ideas in the future. Rework how Java-side unhandled exceptions are processed. Previously, this was handled by the `Android.Runtime.UncaughtExceptionHandler` class, which was constructed during process startup. A side effect of this is that it required going through the (*normal* reflection- based) JNI registration logic of `mono.android.Runtime.register()`, contributing to startup for something that *ideally* would Never Happen™. (Nobody likes unhandled exceptions.) Changes this so that instead of a C#-side `UncaughtExceptionHandler` type we instead have a Java-side `mono.android.XamarinUncaughtExceptionHandler` type which is created and provided to `java.lang.Thread.setDefaultUncaughtExceptionHandler()` during startup. `XamarinUncaughtExceptionHandler` doesn't do anything until Java calls `XamarinUncaughtExceptionHandler.uncaughtException()`, which in turn invokes `Runtime.propagateUncaughtException()`, which in turn does what `UncaughtExceptionHandler` previously did: lookup `AppDomain.DoUnhandledException()` via Reflection and invoke it, thus raising the `AppDomain.UnhandledException` event. In this manner all overheads associated with unhandled exceptions are delayed until the exception happens, with minimal impact on startup. Gains in startup time are quite nice: * Device name: **Pixel 3 XL** * Device architecture: **arm64-v8a** * Number of test runs: **10** * Test application: **Xamarin.Forms integration test** | | **Native to managed** | **Runtime init** | **Displayed** | **Notes** | |-----------------|------------------------|------------------|---------------|--------------------------------| | **master** | 131.278 | 149.074 | 789.10 | preload enabled; 32-bit build | | **this commit** | 49.446 | 66.989 | 764.30 | | | **master** | 132.315 | 147.187 | 795.60 | preload disabled; 32-bit build | | **this commit** | 48.876 | 63.822 | 754.30 | | | **master** | 121.544 | 137.736 | 728.20 | preload enabled; 64-bit build | | **this commit** | 45.350 | 61.464 | 699.50 | | | **master** | 123.448 | 137.052 | 727.40 | preload disabled; 64-bit build | | **this commit** | 44.765 | 58.047 | 689.00 | | * Device name: **Pixel 3 XL** * Device architecture: **arm64-v8a** * Number of test runs: **10** * Test application: Xamarin.Forms "Hello World" app with one label | | **Native to managed** | **Runtime init** | **Displayed** | **Notes** | |-----------------|------------------------|------------------|---------------|--------------------------------| | **master** | 122.090 | 142.004 | 639.00 | preload enabled; 32-bit build | | **this commit** | 44.370 | 63.803 | 586.10 | | | **master** | 121.110 | 134.378 | 634.20 | preload disabled; 32-bit build | | **this commit** | 45.085 | 57.992 | 580.40 | | | **master** | 120.973 | 141.235 | 637.20 | preload enabled; 64-bit build | | **this commit** | 44.767 | 63.846 | 578.50 | | | **master** | 120.785 | 134.588 | 627.00 | preload disabled; 64-bit build | | **this commit** | 44.859 | 57.590 | 575.40 | | [0]: dotnet/java-interop@7d51163 [2]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives [3]: dotnet/java-interop@b33d183
Java.Interop uses the
JniAddNativeMethodRegistrationAttributecustomattribute to determine whether to register native methods for a given
type. It turns out that in most cases there isn't a single instance of
this attribute throughout the application and all the calls to
GetCustomAttributeend up returning nothing while incurring a largeperformance penalty on application startup.
Eliminate the call after checking that no type in the application is
decorated with the above custom attribute.
Another observation made while profiling a couple of XA applications was
that the installation of the uncaught exception handler with
Java.Lang.Threadwas quite expensive while, in most cases, completelynot useful during startup (and, hopefully, during application lifetime).
Instead of registering a managed type to handle the exceptions, register
the handler in Java which then calls down to Xamarin.Android native
runtime which, in turn, propagates the exception to the managed code.
Gains in startup time are quite nice:
Device name: Pixel 3 XL
Device architecture: arm64-v8a
Number of test runs: 10
Test application: Xamarin.Forms integration test
Device name: Pixel 3 XL
Device architecture: arm64-v8a
Number of test runs: 10
Test application: Xamarin.Forms "Hello World" app with one label