Skip to content

Commit

Permalink
Enable lazy loading of AOTd assemblies (#6940)
Browse files Browse the repository at this point in the history
Fixes: #6935

Context: dotnet/runtime#67024

Add support for enabling lazy loading of AOTd assemblies and their
associated shared libraries, when targeting .NET6+.

The feature is enabled by default when AOT is enabled for `Release`
builds and can be disabled by setting the new
`$(AndroidAotEnableLazyLoad)` MSBuild property to `false`.

Performance gains are obvious, but their scale depends on where we look.
Results of running a Hello World MAUI app on Pixel 6 Pro, with:

See PR for full timing details.

With:

  * `$(RuntimeIdentifier)`=android-arm64
  * `$(AndroidEnablePreloadAssemblies)`=False
  * `$(AndroidUseAssemblyStore)`=True, `$(AndroidEnableAssemblyCompression)`=True

| Scenario                              |    Before |     After |         Δ |
| ------------------------------------- | --------: | --------: | --------: |
| `ActivityTaskManager: Displayed` time |   367.800 |   368.200 |  +0.11% ✗ |
| `JNIEnv.Initialize()` time            |     6.221 |     5.367 | -13.73% ✓ |
| Total native init time                |    39.047 |    20.395 | -47.77% ✓ |

With:

  * `$(RuntimeIdentifier)`=android-arm
  * `$(AndroidEnablePreloadAssemblies)`=True
  * `$(AndroidUseAssemblyStore)`=True, `$(AndroidEnableAssemblyCompression)`=True

| Scenario                              |    Before |     After |         Δ |
| ------------------------------------- | --------: | --------: | --------: |
| `ActivityTaskManager: Displayed` time |   490.600 |   475.000 |  -3.18% ✓ |
| `JNIEnv.Initialize()` time            |     8.602 |     7.787 |  -9.48% ✓ |
| Total native init time                |    47.726 |    27.156 | -43.10% ✓ |
  • Loading branch information
grendello committed Apr 25, 2022
1 parent d4a7a7e commit 6dc426f
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 21 deletions.
8 changes: 8 additions & 0 deletions Documentation/guides/building-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ Added in Xamarin.Android 10.1.

The port that `aprofutil` should connect to when obtaining profiling data.

## AndroidAotEnableLazyLoad

Enable lazy (delayed) loading of AOT-d assemblies, instead of
preloading them at the startup. Defaults to `True` for Release builds
with any form of AOT enabled.

Introduced in .NET 6.

## AndroidApkDigestAlgorithm

A string value which specifies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public class GeneratePackageManagerJava : AndroidTask
public string Debug { get; set; }
public ITaskItem[] Environments { get; set; }
public string AndroidAotMode { get; set; }
public bool AndroidAotEnableLazyLoad { get; set; }
public bool EnableLLVM { get; set; }
public string HttpClientHandlerType { get; set; }
public string TlsProvider { get; set; }
Expand Down Expand Up @@ -408,6 +409,7 @@ void AddEnvironment ()
UsesMonoLLVM = EnableLLVM,
UsesAssemblyPreload = usesAssemblyPreload,
MonoAOTMode = aotMode.ToString ().ToLowerInvariant (),
AotEnableLazyLoad = AndroidAotEnableLazyLoad,
AndroidPackageName = AndroidPackageName,
BrokenExceptionTransitions = brokenExceptionTransitions,
PackageNamingPolicy = pnp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public sealed class ApplicationConfig
{
public bool uses_mono_llvm;
public bool uses_mono_aot;
public bool aot_lazy_load;
public bool uses_assembly_preload;
public bool is_a_bundled_app;
public bool broken_exception_transitions;
Expand All @@ -62,7 +63,7 @@ public sealed class ApplicationConfig
public uint mono_components_mask;
public string android_package_name;
};
const uint ApplicationConfigFieldCount = 22;
const uint ApplicationConfigFieldCount = 23;

const string ApplicationConfigSymbolName = "application_config";
const string AppEnvironmentVariablesSymbolName = "app_environment_variables";
Expand Down Expand Up @@ -210,102 +211,108 @@ static ApplicationConfig ReadApplicationConfig (EnvironmentFile envFile)
ret.uses_mono_aot = ConvertFieldToBool ("uses_mono_aot", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 2: // uses_assembly_preload: bool / .byte
case 2:
// aot_lazy_load: bool / .byte
AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
ret.uses_mono_aot = ConvertFieldToBool ("aot_lazy_load", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 3: // uses_assembly_preload: bool / .byte
AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
ret.uses_assembly_preload = ConvertFieldToBool ("uses_assembly_preload", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 3: // is_a_bundled_app: bool / .byte
case 4: // is_a_bundled_app: bool / .byte
AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
ret.is_a_bundled_app = ConvertFieldToBool ("is_a_bundled_app", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 4: // broken_exception_transitions: bool / .byte
case 5: // broken_exception_transitions: bool / .byte
AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
ret.broken_exception_transitions = ConvertFieldToBool ("broken_exception_transitions", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 5: // instant_run_enabled: bool / .byte
case 6: // instant_run_enabled: bool / .byte
AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
ret.instant_run_enabled = ConvertFieldToBool ("instant_run_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 6: // jni_add_native_method_registration_attribute_present: bool / .byte
case 7: // jni_add_native_method_registration_attribute_present: bool / .byte
AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
ret.jni_add_native_method_registration_attribute_present = ConvertFieldToBool ("jni_add_native_method_registration_attribute_present", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 7: // have_runtime_config_blob: bool / .byte
case 8: // have_runtime_config_blob: bool / .byte
AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
ret.have_runtime_config_blob = ConvertFieldToBool ("have_runtime_config_blob", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 8: // have_assemblies_blob: bool / .byte
case 9: // have_assemblies_blob: bool / .byte
AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
ret.have_assemblies_blob = ConvertFieldToBool ("have_assemblies_blob", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 9: // bound_stream_io_exception_type: byte / .byte
case 10: // bound_stream_io_exception_type: byte / .byte
AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
ret.bound_stream_io_exception_type = ConvertFieldToByte ("bound_stream_io_exception_type", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 10: // package_naming_policy: uint32_t / .word | .long
case 11: // package_naming_policy: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.package_naming_policy = ConvertFieldToUInt32 ("package_naming_policy", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 11: // environment_variable_count: uint32_t / .word | .long
case 12: // environment_variable_count: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.environment_variable_count = ConvertFieldToUInt32 ("environment_variable_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 12: // system_property_count: uint32_t / .word | .long
case 13: // system_property_count: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.system_property_count = ConvertFieldToUInt32 ("system_property_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 13: // number_of_assemblies_in_apk: uint32_t / .word | .long
case 14: // number_of_assemblies_in_apk: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.number_of_assemblies_in_apk = ConvertFieldToUInt32 ("number_of_assemblies_in_apk", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 14: // bundled_assembly_name_width: uint32_t / .word | .long
case 15: // bundled_assembly_name_width: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.bundled_assembly_name_width = ConvertFieldToUInt32 ("bundled_assembly_name_width", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 15: // number_of_assembly_store_files: uint32_t / .word | .long
case 16: // number_of_assembly_store_files: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.number_of_assembly_store_files = ConvertFieldToUInt32 ("number_of_assembly_store_files", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 16: // number_of_dso_cache_entries: uint32_t / .word | .long
case 17: // number_of_dso_cache_entries: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 17: // android_runtime_jnienv_class_token: uint32_t / .word | .long
case 18: // android_runtime_jnienv_class_token: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("android_runtime_jnienv_class_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 18: // jnienv_initialize_method_token: uint32_t / .word | .long
case 19: // jnienv_initialize_method_token: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_initialize_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 19: // jnienv_registerjninatives_method_token: uint32_t / .word | .long
case 20: // jnienv_registerjninatives_method_token: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_registerjninatives_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 20: // mono_components_mask: uint32_t / .word | .long
case 21: // mono_components_mask: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
break;

case 21: // android_package_name: string / [pointer type]
case 22: // android_package_name: string / [pointer type]
Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
pointers.Add (field [1].Trim ());
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ sealed class ApplicationConfig
{
public bool uses_mono_llvm;
public bool uses_mono_aot;
public bool aot_lazy_load;
public bool uses_assembly_preload;
public bool is_a_bundled_app;
public bool broken_exception_transitions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ sealed class XamarinAndroidBundledAssembly
public bool UsesMonoLLVM { get; set; }
public bool UsesAssemblyPreload { get; set; }
public string MonoAOTMode { get; set; }
public bool AotEnableLazyLoad { get; set; }
public string AndroidPackageName { get; set; }
public bool BrokenExceptionTransitions { get; set; }
public global::Android.Runtime.BoundExceptionType BoundExceptionType { get; set; }
Expand Down Expand Up @@ -192,6 +193,7 @@ public override void Init ()
var app_cfg = new ApplicationConfig {
uses_mono_llvm = UsesMonoLLVM,
uses_mono_aot = UsesMonoAOT,
aot_lazy_load = AotEnableLazyLoad,
uses_assembly_preload = UsesAssemblyPreload,
is_a_bundled_app = IsBundledApp,
broken_exception_transitions = BrokenExceptionTransitions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<_AndroidAotStripLibraries Condition=" '$(_AndroidAotStripLibraries)' == '' And '$(AndroidIncludeDebugSymbols)' != 'true' ">True</_AndroidAotStripLibraries>
<AndroidUseAssemblyStore Condition=" '$(AndroidUseAssemblyStore)' == '' and ('$(EmbedAssembliesIntoApk)' != 'true' or '$(AndroidIncludeDebugSymbols)' == 'true' or '$(BundleAssemblies)' == 'true') ">false</AndroidUseAssemblyStore>
<AndroidUseAssemblyStore Condition=" '$(AndroidUseAssemblyStore)' == '' ">true</AndroidUseAssemblyStore>
<AndroidAotEnableLazyLoad Condition=" '$(AndroidAotEnableLazyLoad)' == '' And '$(AotAssemblies)' == 'true' And '$(AndroidIncludeDebugSymbols)' != 'true' ">True</AndroidAotEnableLazyLoad>
</PropertyGroup>

<!-- Do not resolve from the GAC under any circumstances in Mobile -->
Expand Down Expand Up @@ -1604,6 +1605,7 @@ because xbuild doesn't support framework reference assemblies.
Manifest="$(IntermediateOutputPath)android\AndroidManifest.xml"
Environments="@(AndroidEnvironment);@(LibraryEnvironments)"
AndroidAotMode="$(AndroidAotMode)"
AndroidAotEnableLazyLoad="$(AndroidAotEnableLazyLoad)"
EnableLLVM="$(EnableLLVM)"
HttpClientHandlerType="$(AndroidHttpClientHandlerType)"
TlsProvider="$(AndroidTlsProvider)"
Expand Down
1 change: 1 addition & 0 deletions src/monodroid/jni/application_dso_stub.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ constexpr char android_package_name[] = "com.xamarin.test";
const ApplicationConfig application_config = {
.uses_mono_llvm = false,
.uses_mono_aot = false,
.aot_lazy_load = false,
.uses_assembly_preload = false,
.is_a_bundled_app = false,
.broken_exception_transitions = false,
Expand Down
7 changes: 7 additions & 0 deletions src/monodroid/jni/monodroid-glue-internal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,17 @@
#undef NDEBUG_UNDEFINE
#endif

//#include <mono/utils/mono-publib.h>
#include <mono/jit/mono-private-unstable.h>
#include <mono/metadata/mono-private-unstable.h>
#endif

#if defined (NET6)
// See https://github.com/dotnet/runtime/pull/67024
// See https://github.com/xamarin/xamarin-android/issues/6935
extern mono_bool mono_opt_aot_lazy_assembly_load;
#endif // def NET6

namespace xamarin::android::internal
{
struct PinvokeEntry
Expand Down
2 changes: 2 additions & 0 deletions src/monodroid/jni/monodroid-glue.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2144,6 +2144,8 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl
jstring_wrapper &home = applicationDirs[SharedConstants::APP_DIRS_FILES_DIR_INDEX];

#if defined (NET6)
mono_opt_aot_lazy_assembly_load = application_config.aot_lazy_load ? TRUE : FALSE;

{
MonoVMProperties monovm_props { home };

Expand Down
1 change: 1 addition & 0 deletions src/monodroid/jni/xamarin-app.hh
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ struct ApplicationConfig
{
bool uses_mono_llvm;
bool uses_mono_aot;
bool aot_lazy_load;
bool uses_assembly_preload;
bool is_a_bundled_app;
bool broken_exception_transitions;
Expand Down

0 comments on commit 6dc426f

Please sign in to comment.