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

[runtime] Optionally preload all the assemblies from the apk #2724

Merged
merged 2 commits into from
Feb 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Documentation/guides/BuildProcess.md
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,30 @@ when packaing Release applications.

Added in Xamarin.Android 8.3.

- **AndroidEnablePreloadAssemblies** – A boolean property which controls
whether or not all managed assemblies bundled within the application package
are loaded during process startup or not.

When set to `True`, all assemblies bundled within the application package
will be loaded during process startup, before any application code is invoked.
This is consistent with what Xamarin.Android did in releases prior to
Xamarin.Andorid 9.2.

When set to `False`, assemblies will only be loaded on an as-needed basis.
This allows applications to startup faster, and is also more consistent with
desktop .NET semantics. To see the time savings, set the `debug.mono.log`
System Property to include `timing`, and look for the
`Finished loading assemblies: preloaded` message within `adb logcat`.

Applications or libraries which use dependency injection may *require* that
this property be `True` if they in turn require that
`AppDomain.CurrentDomain.GetAssemblies()` return all assemblies within the
application bundle, even if the assembly wouldn't otherwise have been needed.

By default this value will be set to `True`.

Added in Xamarin.Android 9.2.

### Binding Project Build Properties

The following MSBuild properties are used with
Expand Down
20 changes: 19 additions & 1 deletion src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<_AndroidDesignTimeBuildPropertiesCache>$(_AndroidIntermediateDesignTimeBuildDirectory)build.props</_AndroidDesignTimeBuildPropertiesCache>
<AndroidGenerateJniMarshalMethods Condition=" '$(AndroidGenerateJniMarshalMethods)' == '' ">False</AndroidGenerateJniMarshalMethods>
<AndroidMakeBundleKeepTemporaryFiles Condition=" '$(AndroidMakeBundleKeepTemporaryFiles)' == '' ">False</AndroidMakeBundleKeepTemporaryFiles>

<!-- If true it will cause all the assemblies in the apk to be preloaded on startup time -->
<AndroidEnablePreloadAssemblies Condition=" '$(AndroidEnablePreloadAssemblies)' == '' ">True</AndroidEnablePreloadAssemblies>
</PropertyGroup>

<Choose>
Expand Down Expand Up @@ -2386,8 +2389,23 @@ because xbuild doesn't support framework reference assemblies.
</GetAddOnPlatformLibraries>
</Target>

<Target Name="_SetupAssemblyPreload"
Condition=" '$(AndroidEnablePreloadAssemblies)' != 'True' "
Inputs="$(IntermediateOutputPath)javastubs.cache"
Copy link
Member

Choose a reason for hiding this comment

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

I don't immediately understand why this would use $(IntermediateOutputPath)javastubs.cache as an input...though that is what _SetupEmbeddedDSOs does...

Copy link
Member

Choose a reason for hiding this comment

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

I don't think an appropriate Inputs/Outputs exists for this target, since there isn't a file that would influence what this does. I think we could just use <WriteLinesToFile WriteOnlyWhenDifferent="True"/> and not use any Inputs/Outputs. msbuild docs

_GenerateJavaStubs writes javastubs.cache looking at android:extractNativeLibs="false", which determines the value of $(_EmbeddedDSOsEnabled). So that makes sense for _SetupEmbeddedDSOs to use javastubs.cache as an input.

Outputs="$(IntermediateOutputPath)preloadAssembliesEnvironment.txt">
<WriteLinesToFile
File="$(IntermediateOutputPath)preloadAssembliesEnvironment.txt"
Lines="mono.enable_assembly_preload=0"
Overwrite="True"
/>
<ItemGroup>
<AndroidEnvironment Include="$(IntermediateOutputPath)preloadAssembliesEnvironment.txt" />
<FileWrites Include="$(IntermediateOutputPath)preloadAssembliesEnvironment.txt" />
</ItemGroup>
</Target>

<Target Name="_GeneratePackageManagerJava"
DependsOnTargets="_GetAddOnPlatformLibraries;_AddStaticResources;$(_AfterAddStaticResources);_PrepareAssemblies"
DependsOnTargets="_GetAddOnPlatformLibraries;_AddStaticResources;$(_AfterAddStaticResources);_PrepareAssemblies;_SetupAssemblyPreload"
Inputs="$(MSBuildAllProjects);$(_ResolvedUserAssembliesHashFile);$(MSBuildProjectFile);$(_AndroidBuildPropertiesCache)"
Outputs="$(_AndroidStampDirectory)_GeneratePackageManagerJava.stamp">
<!-- Create java needed for Mono runtime -->
Expand Down
10 changes: 10 additions & 0 deletions src/monodroid/jni/android-system.cc
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,16 @@ AndroidSystem::setup_environment (jstring_wrapper& name, jstring_wrapper& value)
knownEnvVars.MonoLLVM = true;
return;
}

if (strcmp (k, "mono.enable_assembly_preload") == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

...and regardless of what the default is, this should actually use the value specified. If someone does:

mono.enable_assembly_preload=0

i.e. explicitly disable it via a user-provided @(AndroidEnvironment) file, then it should be disabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

if (*v == '\0')
knownEnvVars.EnableAssemblyPreload = KnownEnvironmentVariables::AssemblyPreloadDefault;
else if (v[0] == '1')
knownEnvVars.EnableAssemblyPreload = true;
else
knownEnvVars.EnableAssemblyPreload = false;
return;
}
}

add_system_property (k, v);
Expand Down
7 changes: 7 additions & 0 deletions src/monodroid/jni/android-system.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ namespace xamarin { namespace android { namespace internal

struct KnownEnvironmentVariables
{
static constexpr bool AssemblyPreloadDefault = true;

bool DSOInApk = false;
MonoAotMode MonoAOT = MonoAotMode::MONO_AOT_MODE_NONE;
bool MonoLLVM = false;
bool EnableAssemblyPreload = AssemblyPreloadDefault;
};

class AndroidSystem
Expand Down Expand Up @@ -133,6 +136,10 @@ namespace xamarin { namespace android { namespace internal
#if defined (WINDOWS)
int setenv (const char *name, const char *value, int overwrite);
#endif
bool is_assembly_preload_enabled () const
{
return knownEnvVars.EnableAssemblyPreload;
}

bool is_mono_llvm_enabled () const
{
Expand Down
66 changes: 61 additions & 5 deletions src/monodroid/jni/monodroid-glue.cc
Original file line number Diff line number Diff line change
Expand Up @@ -957,7 +957,7 @@ mono_runtime_init (char *runtime_args)
}

static MonoDomain*
create_domain (JNIEnv *env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jstring assembly, jobject loader, bool is_root_domain)
create_domain (JNIEnv *env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jobject loader, bool is_root_domain)
{
MonoDomain *domain;
int user_assemblies_count = 0;;
Expand Down Expand Up @@ -1779,21 +1779,75 @@ _monodroid_counters_dump (const char *format, ...)
monoFunctions.counters_dump (XA_LOG_COUNTERS, counters);
}

static void
load_assembly (MonoDomain *domain, JNIEnv *env, jstring_wrapper &assembly)
{
timing_period total_time;
if (XA_UNLIKELY (utils.should_log (LOG_TIMING)))
total_time.mark_start ();

const char *assm_name = assembly.get_cstr ();
MonoAssemblyName *aname;

aname = monoFunctions.assembly_name_new (assm_name);

if (domain != monoFunctions.domain_get ()) {
MonoDomain *current = monoFunctions.domain_get ();
monoFunctions.domain_set (domain, FALSE);
monoFunctions.assembly_load_full (aname, NULL, NULL, 0);
monoFunctions.domain_set (current, FALSE);
} else {
monoFunctions.assembly_load_full (aname, NULL, NULL, 0);
}

monoFunctions.assembly_name_free (aname);

if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) {
total_time.mark_end ();

timing_diff diff (total_time);
log_info (LOG_TIMING, "Assembly load: %s preloaded; elapsed: %lis:%lu::%lu", assm_name, diff.sec, diff.ms, diff.ns);
}
}

static void
load_assemblies (MonoDomain *domain, JNIEnv *env, jstring_array_wrapper &assemblies)
{
timing_period total_time;
if (XA_UNLIKELY (utils.should_log (LOG_TIMING)))
total_time.mark_start ();

/* skip element 0, as that's loaded in create_domain() */
for (size_t i = 1; i < assemblies.get_length (); ++i) {
jstring_wrapper &assembly = assemblies [i];
load_assembly (domain, env, assembly);
}

if (XA_UNLIKELY (utils.should_log (LOG_TIMING))) {
total_time.mark_end ();

timing_diff diff (total_time);
log_info (LOG_TIMING, "Finished loading assemblies: preloaded %u assemblies; wasted time: %lis:%lu::%lu", assemblies.get_length (), diff.sec, diff.ms, diff.ns);
}
}

static void
monodroid_Mono_UnhandledException_internal (MonoException *ex)
{
// Do nothing with it here, we let the exception naturally propagate on the managed side
}

static MonoDomain*
create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jobjectArray assemblies, jobject loader, bool is_root_domain)
create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jstring_array_wrapper &assemblies, jobject loader, bool is_root_domain)
Copy link
Contributor

Choose a reason for hiding this comment

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

@grendello I take it this means we can't get rid of the list of assemblies being passed in from the Java side now?

Copy link
Member

Choose a reason for hiding this comment

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

@dellis1972: that's exactly what this means

Copy link
Contributor Author

Choose a reason for hiding this comment

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

alas

{
MonoDomain* domain = create_domain (env, runtimeClass, runtimeApks, reinterpret_cast <jstring> (env->GetObjectArrayElement (assemblies, 0)), loader, is_root_domain);
MonoDomain* domain = create_domain (env, runtimeClass, runtimeApks, loader, is_root_domain);

// When running on desktop, the root domain is only a dummy so don't initialize it
if (is_running_on_desktop && is_root_domain)
return domain;

if (androidSystem.is_assembly_preload_enabled ())
load_assemblies (domain, env, assemblies);
init_android_runtime (domain, env, runtimeClass, loader);

osBridge.add_monodroid_domain (domain);
Expand All @@ -1804,7 +1858,7 @@ create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wr
JNIEXPORT void JNICALL
Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobjectArray runtimeApksJava,
jstring runtimeNativeLibDir, jobjectArray appDirs, jobject loader,
jobjectArray externalStorageDirs, jobjectArray assemblies, jstring packageName,
jobjectArray externalStorageDirs, jobjectArray assembliesJava, jstring packageName,
jint apiLevel, jobjectArray environmentVariables)
{
init_logging_categories ();
Expand Down Expand Up @@ -1987,6 +2041,7 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject
log_info_nocheck (LOG_TIMING, "Runtime.init: Mono runtime init; elapsed: %lis:%lu::%lu", diff.sec, diff.ms, diff.ns);
}

jstring_array_wrapper assemblies (env, assembliesJava);
/* the first assembly is used to initialize the AppDomain name */
create_and_initialize_domain (env, klass, runtimeApks, assemblies, loader, /*is_root_domain:*/ true);

Expand Down Expand Up @@ -2070,14 +2125,15 @@ reinitialize_android_runtime_type_manager (JNIEnv *env)
}

JNIEXPORT jint
JNICALL Java_mono_android_Runtime_createNewContext (JNIEnv *env, jclass klass, jobjectArray runtimeApksJava, jobjectArray assemblies, jobject loader)
JNICALL Java_mono_android_Runtime_createNewContext (JNIEnv *env, jclass klass, jobjectArray runtimeApksJava, jobjectArray assembliesJava, jobject loader)
{
log_info (LOG_DEFAULT, "CREATING NEW CONTEXT");
reinitialize_android_runtime_type_manager (env);
MonoDomain *root_domain = monoFunctions.get_root_domain ();
monoFunctions.jit_thread_attach (root_domain);

jstring_array_wrapper runtimeApks (env, runtimeApksJava);
jstring_array_wrapper assemblies (env, assembliesJava);
MonoDomain *domain = create_and_initialize_domain (env, klass, runtimeApks, assemblies, loader, /*is_root_domain:*/ false);
monoFunctions.domain_set (domain, FALSE);
int domain_id = monoFunctions.domain_get_id (domain);
Expand Down
2 changes: 2 additions & 0 deletions tests/Xamarin.Forms-Performance-Integration/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

#if !DEBUG
[assembly: XamlCompilation (XamlCompilationOptions.Compile)]
#endif

namespace Xamarin.Forms.Performance.Integration
{
Expand Down
5 changes: 5 additions & 0 deletions tests/Xamarin.Forms-Performance-Integration/Global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.featureHeader {
font-size: 20;
font-style: bold;
margin: 0,20,0,10;
}
48 changes: 0 additions & 48 deletions tests/Xamarin.Forms-Performance-Integration/Views/MainPage.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Xamarin.Forms.Performance.Integration.MainPage"
Resources="{StyleSheet Source=../Global.css}">
</TabbedPage>
50 changes: 50 additions & 0 deletions tests/Xamarin.Forms-Performance-Integration/Views/MainPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;

using Xamarin.Forms;

namespace Xamarin.Forms.Performance.Integration
{
public partial class MainPage : TabbedPage
{
public MainPage ()
{
InitializeComponent ();

Page itemsPage, aboutPage = null;

switch (Device.RuntimePlatform) {
case Device.iOS:
itemsPage = new NavigationPage (new ItemsPage ()) {
Title = "Browse"
};

aboutPage = new NavigationPage (new AboutPage ()) {
Title = "About"
};
itemsPage.Icon = "tab_feed.png";
aboutPage.Icon = "tab_about.png";
break;
default:
itemsPage = new ItemsPage () {
Title = "Browse"
};

aboutPage = new AboutPage () {
Title = "About"
};
break;
}

Children.Add (itemsPage);
Children.Add (aboutPage);

Title = Children [0].Title;
}

protected override void OnCurrentPageChanged ()
{
base.OnCurrentPageChanged ();
Title = CurrentPage?.Title ?? string.Empty;
}
}
}