Skip to content

Commit 5fe8523

Browse files
committed
Startup performance improvements (less reflection)
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** | 50.637 | 68.323 | 783.50 | | | **master** | 132.315 | 147.187 | 795.60 | preload disabled; 32-bit build | | **this commit** | 50.972 | 65.785 | 787.90 | | | **master** | 121.544 | 137.736 | 728.20 | preload enabled; 64-bit build | | **this commit** | 47.804 | 64.003 | 739.70 | | | **master** | 123.448 | 137.052 | 727.40 | preload disabled; 64-bit build | | **this commit** | 47.274 | 60.772 | 742.20 | | 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** | 49.272 | 69.298 | 654.20 | | | **master** | 121.110 | 134.378 | 634.20 | preload disabled; 32-bit build | | **this commit** | 47.719 | 60.788 | 616.00 | | | **master** | 120.973 | 141.235 | 637.20 | preload enabled; 64-bit build | | **this commit** | 48.642 | 68.290 | 626.00 | | | **master** | 120.785 | 134.588 | 627.00 | preload disabled; 64-bit build | | **this commit** | 48.657 | 63.290 | 625.00 | |
1 parent 89a4524 commit 5fe8523

19 files changed

Lines changed: 157 additions & 111 deletions

File tree

.gitmodules

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
branch = v1.1.1
1313
[submodule "external/Java.Interop"]
1414
path = external/Java.Interop
15-
url = https://github.com/xamarin/java.interop.git
16-
branch = master
15+
url = https://github.com/grendello/java.interop.git
16+
branch = no-reflection
1717
[submodule "external/mman-win32"]
1818
path = external/mman-win32
1919
url = https://github.com/witwall/mman-win32.git

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,18 @@ namespace Android.Runtime {
1515

1616
class AndroidRuntime : JniRuntime {
1717

18-
internal AndroidRuntime (IntPtr jnienv, IntPtr vm, bool allocNewObjectSupported, IntPtr classLoader, IntPtr classLoader_loadClass)
19-
: base (new AndroidRuntimeOptions (jnienv, vm, allocNewObjectSupported, classLoader, classLoader_loadClass))
18+
internal AndroidRuntime (IntPtr jnienv,
19+
IntPtr vm,
20+
bool allocNewObjectSupported,
21+
IntPtr classLoader,
22+
IntPtr classLoader_loadClass,
23+
bool jniAddNativeMethodRegistrationAttributePresent)
24+
: base (new AndroidRuntimeOptions (jnienv,
25+
vm,
26+
allocNewObjectSupported,
27+
classLoader,
28+
classLoader_loadClass,
29+
jniAddNativeMethodRegistrationAttributePresent))
2030
{
2131
}
2232

@@ -67,18 +77,23 @@ public override void RaisePendingException (Exception pendingException)
6777
}
6878

6979
class AndroidRuntimeOptions : JniRuntime.CreationOptions {
70-
71-
public AndroidRuntimeOptions (IntPtr jnienv, IntPtr vm, bool allocNewObjectSupported, IntPtr classLoader, IntPtr classLoader_loadClass)
80+
public AndroidRuntimeOptions (IntPtr jnienv,
81+
IntPtr vm,
82+
bool allocNewObjectSupported,
83+
IntPtr classLoader,
84+
IntPtr classLoader_loadClass,
85+
bool jniAddNativeMethodRegistrationAttributePresent)
7286
{
7387
EnvironmentPointer = jnienv;
7488
ClassLoader = new JniObjectReference (classLoader, JniObjectReferenceType.Global);
7589
ClassLoader_LoadClass_id= classLoader_loadClass;
7690
InvocationPointer = vm;
7791
NewObjectRequired = !allocNewObjectSupported;
7892
ObjectReferenceManager = new AndroidObjectReferenceManager ();
79-
TypeManager = new AndroidTypeManager ();
93+
TypeManager = new AndroidTypeManager (jniAddNativeMethodRegistrationAttributePresent);
8094
ValueManager = new AndroidValueManager ();
8195
UseMarshalMemberBuilder = false;
96+
JniAddNativeMethodRegistrationAttributePresent = jniAddNativeMethodRegistrationAttributePresent;
8297
}
8398
}
8499

@@ -219,6 +234,12 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value)
219234
}
220235

221236
class AndroidTypeManager : JniRuntime.JniTypeManager {
237+
bool jniAddNativeMethodRegistrationAttributePresent;
238+
239+
public AndroidTypeManager (bool jniAddNativeMethodRegistrationAttributePresent)
240+
{
241+
this.jniAddNativeMethodRegistrationAttributePresent = jniAddNativeMethodRegistrationAttributePresent;
242+
}
222243

223244
protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpleReference)
224245
{
@@ -347,13 +368,15 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, stri
347368
return;
348369

349370
if (string.IsNullOrEmpty (methods)) {
350-
base.RegisterNativeMembers (nativeClass, type, methods);
371+
if (jniAddNativeMethodRegistrationAttributePresent)
372+
base.RegisterNativeMembers (nativeClass, type, methods);
351373
return;
352374
}
353375

354376
string[] members = methods.Split ('\n');
355377
if (members.Length < 2) {
356-
base.RegisterNativeMembers (nativeClass, type, methods);
378+
if (jniAddNativeMethodRegistrationAttributePresent)
379+
base.RegisterNativeMembers (nativeClass, type, methods);
357380
return;
358381
}
359382

src/Mono.Android/Android.Runtime/JNIEnv.cs

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct JnienvInitializeArgs {
3333
public byte brokenExceptionTransitions;
3434
public int packageNamingPolicy;
3535
public byte ioExceptionType;
36+
public int jniAddNativeMethodRegistrationAttributePresent;
3637
}
3738
#pragma warning restore 0649
3839

@@ -54,7 +55,6 @@ public static partial class JNIEnv {
5455
internal static int gref_gc_threshold;
5556

5657
internal static bool PropagateExceptions;
57-
static UncaughtExceptionHandler defaultUncaughtExceptionHandler;
5858

5959
internal static bool IsRunningOnDesktop;
6060
internal static bool LogTypemapMissStackTrace;
@@ -173,7 +173,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
173173
Mono.SystemDependencyProvider.Initialize ();
174174

175175
BoundExceptionType = (BoundExceptionType)args->ioExceptionType;
176-
androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass);
176+
androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0);
177177
AndroidValueManager = (AndroidValueManager) androidRuntime.ValueManager;
178178

179179
AllocObjectSupported = androidSdkVersion > 10;
@@ -183,12 +183,6 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
183183

184184
PropagateExceptions = args->brokenExceptionTransitions == 0;
185185

186-
if (PropagateExceptions) {
187-
defaultUncaughtExceptionHandler = new UncaughtExceptionHandler (Java.Lang.Thread.DefaultUncaughtExceptionHandler);
188-
if (!IsRunningOnDesktop)
189-
Java.Lang.Thread.DefaultUncaughtExceptionHandler = defaultUncaughtExceptionHandler;
190-
}
191-
192186
JavaNativeTypeManager.PackageNamingPolicy = (PackageNamingPolicy)args->packageNamingPolicy;
193187
if (IsRunningOnDesktop) {
194188
string packageNamingPolicy = Environment.GetEnvironmentVariable ("__XA_PACKAGE_NAMING_POLICY__");
@@ -204,13 +198,6 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
204198

205199
internal static void Exit ()
206200
{
207-
/* Reset uncaught exception handler so that we don't mistakenly reuse a
208-
* now-invalid handler the next time we reinitialize JNIEnv.
209-
*/
210-
var uncaughtExceptionHandler = Java.Lang.Thread.DefaultUncaughtExceptionHandler as UncaughtExceptionHandler;
211-
if (uncaughtExceptionHandler != null && uncaughtExceptionHandler == defaultUncaughtExceptionHandler)
212-
Java.Lang.Thread.DefaultUncaughtExceptionHandler = uncaughtExceptionHandler.DefaultHandler;
213-
214201
/* Manually dispose surfaced objects and close the current JniEnvironment to
215202
* avoid ObjectDisposedException thrown on finalizer threads after shutdown
216203
*/
@@ -249,15 +236,56 @@ static void ManualJavaObjectDispose (Java.Lang.Object obj)
249236
GC.SuppressFinalize (obj);
250237
}
251238

239+
static Action<Exception> mono_unhandled_exception;
240+
static Action<AppDomain, UnhandledExceptionEventArgs> AppDomain_DoUnhandledException;
241+
242+
static void InitializeUncaughtExceptionHandling ()
243+
{
244+
if (mono_unhandled_exception == null) {
245+
var mono_UnhandledException = typeof (System.Diagnostics.Debugger)
246+
.GetMethod ("Mono_UnhandledException", BindingFlags.NonPublic | BindingFlags.Static);
247+
mono_unhandled_exception = (Action<Exception>) Delegate.CreateDelegate (typeof(Action<Exception>), mono_UnhandledException);
248+
}
249+
250+
if (AppDomain_DoUnhandledException == null) {
251+
var ad_due = typeof (AppDomain)
252+
.GetMethod ("DoUnhandledException",
253+
bindingAttr: BindingFlags.NonPublic | BindingFlags.Instance,
254+
binder: null,
255+
types: new []{typeof (UnhandledExceptionEventArgs)},
256+
modifiers: null);
257+
if (ad_due != null) {
258+
AppDomain_DoUnhandledException = (Action<AppDomain, UnhandledExceptionEventArgs>) Delegate.CreateDelegate (
259+
typeof (Action<AppDomain, UnhandledExceptionEventArgs>), ad_due);
260+
}
261+
}
262+
}
263+
252264
internal static void PropagateUncaughtException (IntPtr env, IntPtr javaThreadPtr, IntPtr javaExceptionPtr)
253265
{
254-
if (defaultUncaughtExceptionHandler == null)
266+
if (!PropagateExceptions)
255267
return;
256268

257-
var javaThread = JavaObject.GetObject<Java.Lang.Thread> (env, javaThreadPtr, JniHandleOwnership.DoNotTransfer);
269+
try {
270+
InitializeUncaughtExceptionHandling ();
271+
} catch (Exception e) {
272+
Android.Runtime.AndroidEnvironment.FailFast ($"Unable to initialize UncaughtExceptionHandler. Nested exception caught: {e}");
273+
}
274+
258275
var javaException = JavaObject.GetObject<Java.Lang.Throwable> (env, javaExceptionPtr, JniHandleOwnership.DoNotTransfer);
259276

260-
defaultUncaughtExceptionHandler.UncaughtException (javaThread, javaException);
277+
mono_unhandled_exception (javaException);
278+
if (AppDomain_DoUnhandledException == null)
279+
return;
280+
281+
try {
282+
var jltp = javaException as JavaProxyThrowable;
283+
Exception innerException = jltp?.InnerException;
284+
var args = new UnhandledExceptionEventArgs (innerException ?? javaException, isTerminating: true);
285+
AppDomain_DoUnhandledException (AppDomain.CurrentDomain, args);
286+
} catch (Exception e) {
287+
Logger.Log (LogLevel.Error, "monodroid", "Exception thrown while raising AppDomain.UnhandledException event: " + e.ToString ());
288+
}
261289
}
262290

263291
[DllImport ("__Internal", CallingConvention = CallingConvention.Cdecl)]

src/Mono.Android/Android.Runtime/UncaughtExceptionHandler.cs

Lines changed: 0 additions & 69 deletions
This file was deleted.

src/Mono.Android/Android.Runtime/XAPeerMembers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Android.Runtime {
77

88
public class XAPeerMembers : JniPeerMembers {
99

10-
static Dictionary<string, JniPeerMembers> LegacyPeerMembers = new Dictionary<string, JniPeerMembers> ();
10+
static Dictionary<string, JniPeerMembers> LegacyPeerMembers = new Dictionary<string, JniPeerMembers> (StringComparer.Ordinal);
1111

1212
public XAPeerMembers (string jniPeerTypeName, Type managedPeerType)
1313
: base (jniPeerTypeName, managedPeerType)
@@ -73,4 +73,4 @@ static IntPtr GetThresholdClass (IJavaPeerable value)
7373
return IntPtr.Zero;
7474
}
7575
}
76-
}
76+
}

src/Mono.Android/Java.Interop/TypeManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ static class TypeManagerMapDictionaries
2323
public static Dictionary<string, Type> JniToManaged {
2424
get {
2525
if (_jniToManaged == null)
26-
_jniToManaged = new Dictionary<string, Type> ();
26+
_jniToManaged = new Dictionary<string, Type> (StringComparer.Ordinal);
2727
return _jniToManaged;
2828
}
2929
}

src/Mono.Android/Mono.Android.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,6 @@
233233
<Compile Include="Android.Runtime\StringDefAttribute.cs" />
234234
<Compile Include="Android.Runtime\TimingLogger.cs" />
235235
<Compile Include="Android.Runtime\TypeManager.cs" />
236-
<Compile Include="Android.Runtime\UncaughtExceptionHandler.cs" />
237236
<Compile Include="Android.Runtime\XAPeerMembers.cs" />
238237
<Compile Include="Android.Runtime\XmlPullParserReader.cs" />
239238
<Compile Include="Android.Runtime\XmlReaderPullParser.cs" />

src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,9 +400,10 @@ void SaveResource (string resource, string filename, string destDir, Func<string
400400
void WriteTypeMappings (List<TypeDefinition> types)
401401
{
402402
var tmg = new TypeMapGenerator ((string message) => Log.LogDebugMessage (message), SupportedAbis);
403-
if (!tmg.Generate (types, TypemapOutputDirectory, GenerateNativeAssembly))
403+
if (!tmg.Generate (types, TypemapOutputDirectory, GenerateNativeAssembly, out ApplicationConfigTaskState appConfState))
404404
throw new XamarinAndroidException (4308, Properties.Resources.XA4308);
405405
GeneratedBinaryTypeMaps = tmg.GeneratedBinaryTypeMaps.ToArray ();
406+
BuildEngine4.RegisterTaskObject (ApplicationConfigTaskState.RegisterTaskObjectKey, appConfState, RegisteredTaskObjectLifetime.AppDomain, allowEarlyCollection: false);
406407
}
407408
}
408409
}

src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ void AddEnvironment ()
249249
throw new InvalidOperationException ($"Unsupported BoundExceptionType value '{BoundExceptionType}'");
250250
}
251251

252+
var appConfState = BuildEngine4.GetRegisteredTaskObject (ApplicationConfigTaskState.RegisterTaskObjectKey, RegisteredTaskObjectLifetime.AppDomain) as ApplicationConfigTaskState;
252253
foreach (string abi in SupportedAbis) {
253254
NativeAssemblerTargetProvider asmTargetProvider;
254255
string baseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"environment.{abi.ToLowerInvariant ()}");
@@ -285,6 +286,7 @@ void AddEnvironment ()
285286
PackageNamingPolicy = pnp,
286287
BoundExceptionType = boundExceptionType,
287288
InstantRunEnabled = InstantRunEnabled,
289+
JniAddNativeMethodRegistrationAttributePresent = appConfState != null ? appConfState.JniAddNativeMethodRegistrationAttributePresent : false,
288290
};
289291

290292
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {

0 commit comments

Comments
 (0)