Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] Profiled AOT can Full AOT the main asse…
Browse files Browse the repository at this point in the history
…mbly (#6482)

When using the built-in profiles for Profiled AOT, one drawback is
the user's code isn't AOT'd.  We only AOT types that are called in
the BCL, `Java.Interop.dll`, `Mono.Android.dll`, etc.

To make this better, we can AOT the "main" assembly.  Most code in
the users' main assembly would likely get called on startup.  This
should have a minor impact to `.apk` size, but gain us a better
startup boost by default.

When testing, I can see more methods AOT'd:

	11-11 09:01:01.441 16386 16386 D Mono    : AOT: FOUND method foo.MainActivity:OnCreate (Android.OS.Bundle) [0x7c050e8e20f0 - 0x7c050e8e213b 0x7c050e8e2673]

~~ Results ~~

| Test                                  |     File Size (Δ) |    Activity Displayed (Δ) |
| ------------------------------------- | ----------------: | ------------------------: |
|      `dotnet new android` w/o AOT app |        -baseline- |                     193ms |
|   `dotnet new android` *with* AOT app |              (0%) |               192ms (99%) |
|         `dotnet new maui` w/o AOT app |        -baseline- |                     641ms | 
|      `dotnet new maui` *with* AOT app |   +20,480 (0.14%) |               627ms (98%) |

The `dotnet new android` app is built with:

	> dotnet build -c Release -p:AndroidEnableProfiledAot=true -p:RunAOTCompilation=true -r android-arm64 -p:AndroidPackageFormat=apk

File size differences:

	> apkdiff -f before.apk after.apk
	Size difference in bytes ([*1] apk1 only, [*2] apk2 only):
	+       5,632 lib/arm64-v8a/libaot-foo.dll.so
	+           5 assemblies/assemblies.blob
	Summary:
	+           5 Other entries 0.00% (of 787,048)
	+           0 Dalvik executables 0.00% (of 345,340)
	+       5,632 Shared libraries 0.09% (of 6,008,168)
	+           0 Package size difference 0.00% (of 3,169,571)

10 runs on a Pixel 5 was an average of 193ms before, and 192ms after
(the `Activity Displayed` time).  There may not, in fact, be a
difference for `dotnet new android`.

Next I installed .NET MAUI 6.0.101-preview.11.2149, and tried a
`dotnet new maui` app:

File size:

	> apkdiff -f before.apk after.apk
	Size difference in bytes ([*1] apk1 only, [*2] apk2 only):
	+      67,144 lib/arm64-v8a/libaot-foo.dll.so
	Summary:
	+           0 Other entries 0.00% (of 7,765,174)
	+           0 Dalvik executables 0.00% (of 6,443,152)
	+      67,144 Shared libraries 0.50% (of 13,388,688)
	+      20,480 Package size difference 0.14% (of 14,272,332)

Total file size difference:

	--14,251,852
	++14,272,332

The average over 10 runs on a Pixel 5, before applying this change,
is ~641ms.  After applying this change, it's ~627ms.

I think this might help startup by ~14ms in a `dotnet new maui` app.
It would likely improve more depending on the app.
  • Loading branch information
jonathanpeppers committed Nov 16, 2021
1 parent f3de57d commit 8ac4425
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 76 deletions.
Expand Up @@ -30,7 +30,7 @@ They run in a context of an inner build with a single $(RuntimeIdentifier).
<Import Project="Sdk.props" Sdk="Microsoft.NETCore.App.Runtime.AOT.Cross.android-arm64" />
</ImportGroup>

<UsingTask TaskName="Xamarin.Android.Tasks.GetAotArguments" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
<UsingTask TaskName="Xamarin.Android.Tasks.GetAotAssemblies" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />

<Target Name="_AndroidAotInputs">
<ItemGroup>
Expand All @@ -43,39 +43,34 @@ They run in a context of an inner build with a single $(RuntimeIdentifier).
DependsOnTargets="_AndroidAotInputs"
Inputs="@(_AndroidAotInputs)"
Outputs="$(_AndroidStampDirectory)_AndroidAot.stamp">
<GetAotArguments
<ItemGroup>
<AndroidAotProfile Include="$(MSBuildThisFileDirectory)dotnet.aotprofile" Condition=" '$(AndroidEnableProfiledAot)' == 'true' and '$(AndroidUseDefaultAotProfile)' != 'false' " />
</ItemGroup>
<GetAotAssemblies
AndroidAotMode="$(AndroidAotMode)"
AndroidNdkDirectory="$(_AndroidNdkDirectory)"
AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)"
AndroidApiLevel="$(_AndroidApiLevel)"
MinimumSupportedApiLevel="$(AndroidMinimumSupportedApiLevel)"
AndroidSequencePointsMode="$(_SequencePointsMode)"
AotAdditionalArguments="$(AndroidAotAdditionalArguments)"
TargetName="$(TargetName)"
ResolvedAssemblies="@(_AndroidAotInputs)"
AotOutputDirectory="$(_AndroidAotBinDirectory)"
RuntimeIdentifier="$(RuntimeIdentifier)"
EnableLLVM="$(EnableLLVM)"
UsingAndroidNETSdk="true"
Profiles="@(_AotProfiles)">
<Output PropertyName="_AotArguments" TaskParameter="Arguments" />
<Output PropertyName="_AotOutputDirectory" TaskParameter="OutputDirectory" />
</GetAotArguments>
Profiles="@(AndroidAotProfile)">
<Output ItemName="_MonoAOTAssemblies" TaskParameter="ResolvedAssemblies" />
</GetAotAssemblies>
<PropertyGroup>
<_MonoAOTCompilerPath>@(MonoAotCrossCompiler->WithMetadataValue('RuntimeIdentifier', '$(RuntimeIdentifier)'))</_MonoAOTCompilerPath>
<_LLVMPath Condition=" '$(EnableLLVM)' == 'true' ">$([System.IO.Path]::GetDirectoryName ('$(_MonoAOTCompilerPath)'))</_LLVMPath>
</PropertyGroup>
<ItemGroup>
<_MonoAOTAssemblies
Include="@(_AndroidAotInputs->'%(FullPath)')"
TempDirectory="$([MSBuild]::EnsureTrailingSlash($(_AotOutputDirectory)))%(FileName)"
AotArguments="$(_AotArguments),temp-path=$([System.IO.Path]::GetFullPath(%(_MonoAOTAssemblies.TempDirectory)))"
/>
<AndroidAotProfile Include="$(MSBuildThisFileDirectory)dotnet.aotprofile" Condition=" '$(AndroidEnableProfiledAot)' == 'true' and '$(AndroidUseDefaultAotProfile)' != 'false' " />
</ItemGroup>
<MakeDir Directories="$(IntermediateOutputPath)aot\;@(_MonoAOTAssemblies->'%(TempDirectory)')" />
<MakeDir Directories="$(IntermediateOutputPath)aot\" />
<MonoAOTCompiler
Assemblies="@(_MonoAOTAssemblies)"
Assemblies="@(_MonoAOTAssemblies->'%(FullPath)')"
CompilerBinaryPath="$(_MonoAOTCompilerPath)"
AotProfilePath="@(AndroidAotProfile->'%(FullPath)')"
DisableParallelAot="$(_DisableParallelAot)"
IntermediateOutputPath="$(IntermediateOutputPath)"
LibraryFormat="So"
Expand Down
14 changes: 11 additions & 3 deletions src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs
Expand Up @@ -32,9 +32,6 @@ public class Aot : GetAotArguments
{
public override string TaskPrefix => "AOT";

[Required]
public ITaskItem[] ResolvedAssemblies { get; set; }

// Which ABIs to include native libs for
[Required]
public string [] SupportedAbis { get; set; }
Expand Down Expand Up @@ -145,6 +142,17 @@ IEnumerable<Config> GetAotConfigs (NdkTools ndk)
aotOptions.Insert (0, $"outfile={outputFile}");
aotOptions.Insert (0, $"llvm-path={SdkBinDirectory}");
aotOptions.Insert (0, $"temp-path={tempDir}");
if (Profiles != null && Profiles.Length > 0) {
if (Path.GetFileNameWithoutExtension (assembly.ItemSpec) == TargetName) {
LogDebugMessage ($"Not using profile(s) for main assembly: {assembly.ItemSpec}");
} else {
aotOptions.Add ("profile-only");
foreach (var p in Profiles) {
var fp = Path.GetFullPath (p.ItemSpec);
aotOptions.Add ($"profile={fp}");
}
}
}

// we need to quote the entire --aot arguments here to make sure it is parsed
// on windows as one argument. Otherwise it will be split up into multiple
Expand Down
60 changes: 7 additions & 53 deletions src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs
Expand Up @@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Java.Interop.Tools.Diagnostics;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
Expand All @@ -12,12 +11,10 @@ namespace Xamarin.Android.Tasks
{
/// <summary>
/// The <Aot/> task subclasses this in "legacy" Xamarin.Android.
/// Called directly in .NET 5 to populate %(AotArguments) metadata.
/// The <GetAotAssemblies/> task subclasses this in .NET 6+.
/// </summary>
public class GetAotArguments : AndroidAsyncTask
public abstract class GetAotArguments : AndroidAsyncTask
{
public override string TaskPrefix => "GAOT";

[Required]
public string AndroidApiLevel { get; set; }

Expand All @@ -30,6 +27,9 @@ public class GetAotArguments : AndroidAsyncTask
[Required]
public string AndroidBinUtilsDirectory { get; set; }

[Required]
public string TargetName { get; set; }

/// <summary>
/// Will be blank in .NET 6+
/// </summary>
Expand All @@ -54,11 +54,8 @@ public class GetAotArguments : AndroidAsyncTask

public string AotAdditionalArguments { get; set; }

[Output]
public string Arguments { get; set; }

[Output]
public string OutputDirectory { get; set; }
[Required, Output]
public ITaskItem [] ResolvedAssemblies { get; set; }

protected AotMode AotMode;
protected SequencePointsMode SequencePointsMode;
Expand Down Expand Up @@ -110,42 +107,6 @@ public static bool TryGetSequencePointsMode (string value, out SequencePointsMod
return false;
}

public override Task RunTaskAsync ()
{
NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log);
if (ndk == null) {
return Task.CompletedTask; // NdkTools.Create will log appropriate error
}

bool hasValidAotMode = GetAndroidAotMode (AndroidAotMode, out AotMode);
if (!hasValidAotMode) {
LogCodedError ("XA3002", Properties.Resources.XA3002, AndroidAotMode);
return Task.CompletedTask;
}

if (AotMode == AotMode.Interp) {
LogDebugMessage ("Interpreter AOT mode enabled");
return Task.CompletedTask;
}

TryGetSequencePointsMode (AndroidSequencePointsMode, out SequencePointsMode);

SdkBinDirectory = MonoAndroidHelper.GetOSBinPath ();

var abi = AndroidRidAbiHelper.RuntimeIdentifierToAbi (RuntimeIdentifier);
if (string.IsNullOrEmpty (abi)) {
Log.LogCodedError ("XA0035", Properties.Resources.XA0035, RuntimeIdentifier);
return Task.CompletedTask;
}

(_, string outdir, string mtriple, AndroidTargetArch arch) = GetAbiSettings (abi);
string toolPrefix = GetToolPrefix (ndk, arch, out int level);

Arguments = string.Join (",", GetAotOptions (ndk, arch, level, outdir, mtriple, toolPrefix));
OutputDirectory = outdir;
return Task.CompletedTask;
}

protected string GetToolPrefix (NdkTools ndk, AndroidTargetArch arch, out int level)
{
level = 0;
Expand Down Expand Up @@ -252,13 +213,6 @@ protected List<string> GetAotOptions (NdkTools ndk, AndroidTargetArch arch, int
{
List<string> aotOptions = new List<string> ();

if (Profiles != null && Profiles.Length > 0) {
aotOptions.Add ("profile-only");
foreach (var p in Profiles) {
var fp = Path.GetFullPath (p.ItemSpec);
aotOptions.Add ($"profile={fp}");
}
}
if (!string.IsNullOrEmpty (AotAdditionalArguments))
aotOptions.Add (AotAdditionalArguments);
if (SequencePointsMode == SequencePointsMode.Offline)
Expand Down
74 changes: 74 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Tasks/GetAotAssemblies.cs
@@ -0,0 +1,74 @@
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Android.Build.Tasks;
using Xamarin.Android.Tools;

namespace Xamarin.Android.Tasks
{
/// <summary>
/// Called in .NET 6+ to populate %(AotArguments) metadata in the ResolvedAssemblies [Output] property.
/// </summary>
public class GetAotAssemblies : GetAotArguments
{
public override string TaskPrefix => "GAOT";

public override Task RunTaskAsync ()
{
NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log);
if (ndk == null) {
return Task.CompletedTask; // NdkTools.Create will log appropriate error
}

bool hasValidAotMode = GetAndroidAotMode (AndroidAotMode, out AotMode);
if (!hasValidAotMode) {
LogCodedError ("XA3002", Properties.Resources.XA3002, AndroidAotMode);
return Task.CompletedTask;
}

if (AotMode == AotMode.Interp) {
LogDebugMessage ("Interpreter AOT mode enabled");
return Task.CompletedTask;
}

TryGetSequencePointsMode (AndroidSequencePointsMode, out SequencePointsMode);

SdkBinDirectory = MonoAndroidHelper.GetOSBinPath ();

var abi = AndroidRidAbiHelper.RuntimeIdentifierToAbi (RuntimeIdentifier);
if (string.IsNullOrEmpty (abi)) {
Log.LogCodedError ("XA0035", Properties.Resources.XA0035, RuntimeIdentifier);
return Task.CompletedTask;
}

(_, string outdir, string mtriple, AndroidTargetArch arch) = GetAbiSettings (abi);
string toolPrefix = GetToolPrefix (ndk, arch, out int level);

var aotOptions = GetAotOptions (ndk, arch, level, outdir, mtriple, toolPrefix);

var aotProfiles = new StringBuilder ();
if (Profiles != null && Profiles.Length > 0) {
aotProfiles.Append (",profile-only");
foreach (var p in Profiles) {
var fp = Path.GetFullPath (p.ItemSpec);
aotProfiles.Append (",profile=");
aotProfiles.Append (fp);
}
}

var arguments = string.Join (",", aotOptions);
foreach (var assembly in ResolvedAssemblies) {
var temp = Path.GetFullPath (Path.Combine (outdir, Path.GetFileNameWithoutExtension (assembly.ItemSpec)));
Directory.CreateDirectory (temp);
if (Path.GetFileNameWithoutExtension (assembly.ItemSpec) == TargetName) {
LogDebugMessage ($"Not using profile(s) for main assembly: {assembly.ItemSpec}");
assembly.SetMetadata ("AotArguments", $"{arguments},temp-path={temp}");
} else {
assembly.SetMetadata ("AotArguments", $"{arguments},temp-path={temp}{aotProfiles}");
}
}

return Task.CompletedTask;
}
}
}
Expand Up @@ -700,6 +700,7 @@ projects. .NET 5 projects will not import this file.
AndroidSequencePointsMode="$(_SequencePointsMode)"
AotAdditionalArguments="$(AndroidAotAdditionalArguments)"
ExtraAotOptions="$(AndroidExtraAotOptions)"
TargetName="$(TargetName)"
ResolvedAssemblies="@(_ShrunkAssemblies)"
AotOutputDirectory="$(_AndroidAotBinDirectory)"
IntermediateAssemblyDir="$(MonoAndroidIntermediateAssemblyDir)"
Expand Down
Expand Up @@ -245,7 +245,7 @@
"Size": 13656
},
"lib/armeabi-v7a/libaot-Xamarin.Forms.Performance.Integration.Droid.dll.so": {
"Size": 10648
"Size": 260040
},
"lib/armeabi-v7a/libaot-Xamarin.Forms.Platform.Android.dll.so": {
"Size": 1333820
Expand Down Expand Up @@ -389,7 +389,7 @@
"Size": 21520
},
"lib/x86/libaot-Xamarin.Forms.Performance.Integration.Droid.dll.so": {
"Size": 14488
"Size": 178024
},
"lib/x86/libaot-Xamarin.Forms.Platform.Android.dll.so": {
"Size": 1182700
Expand Down Expand Up @@ -2243,5 +2243,5 @@
"Size": 347268
}
},
"PackageSize": 17233092
"PackageSize": 17417412
}

0 comments on commit 8ac4425

Please sign in to comment.