Skip to content

Commit

Permalink
[monodroid] Pre-allocate buffers for embedded assembly names (#6188)
Browse files Browse the repository at this point in the history
When Embedded Assemblies are used (`$(EmbedAssembliesIntoApk)`=True),
assembly names are detected during `.apk` content traversal,
duplicated, and stored into `bundled_assemblies` and
`MonoBundledAssembly::name`.

Reduce memory allocations by updating `libxamarin-app.so` to contain
a `bundled_assembly_names` array, a'la:

	struct XamarinAndroidBundledAssembly {
	    int32_t     apk_fd;
	    uint32_t    data_offset;
	    uint32_t    data_size;
	    uint8_t    *data;
	    uint32_t    name_length;
	    char       *name;
	};
	constexpr size_t AssemblyNameWidth;
	static char assembly_name_1 [AssemblyNameWidth];
	static char assembly_name_2 [AssemblyNameWidth];
	// …

	extern "C" XamarinAndroidBundledAssembly bundled_assemblies[] = {
	    {
	        .apk_fd         = -1,
	        .data_offset    = 0,
	        .data_size      = 0,
	        .name_length	= 0,
	        .name           = assembly_name_1,
	    },
	    {
	        .apk_fd         = -1,
	        .data_offset    = 0,
	        .data_size      = 0,
	        .name_length	= 0,
	        .name           = assembly_name_2,
	    },
	    // …
	}

The `bundled_assemblies` array *doesn't* contain the assembly names
detected during packaging time.  Rather, it contains *buffers*, each
large enough to hold any assembly name.

This removes the need to allocate additional memory for assembly
names during process startup.  (We'll still need to allocate memory
for other entries within the `.apk`, such as `.pdb` files.)

Additionally, a provision is made for adding more assemblies than
those counted and placed in the .`apk` during the build.  This may
come handy when Xamarin.Android supports split applications (with
components placed in different `.apk` files), and also as a simple
fail-safe feature should the build miscalculate something.

Debug information for assemblies is no longer registered when reading
the APK, but rather whenever an assembly load is requested by Mono.

Neither the assemblies nor their debug information are mmapped when
reading the APK anymore.  This is done lazily when assembly load is
requested by Mono.  Mapping of files after the startup phase is
protected with a POSIX semaphore.

Additionally, a handful of simple optimizations are implemented:

  * We no longer query the device's memory page size whenever we
    `mmap()` a file; the value is instead queried and cached in the
    `Util` constructor.
  * The current AppDomain is no longer queried when getting a Mono
    object's type under .NET 6
  * Many log statements are no longer run by default during startup.

These changes reduce startup time for a simple plain
Xamarin.Android application (one control in a layout) by around 4ms
measured for `Displayed` time, and by around 2ms when measured for
native runtime startup.

For the `Hello Maui` sample from the `maui-samples` repository, the
startup time is reduced by ~200ms measured for `Displayed` time (from
~1.6s to ~1.4s), with the same speed up for the native runtime as in
the plain Xamarin.Android test above.

Performance was measured on a Pixel 3XL phone running Android 12 beta.
*Note*: the `Displayed` measurements were found to be **very** unstable
during testing on Android 12, with variance between runs reaching 600ms
sometimes.
  • Loading branch information
grendello committed Aug 18, 2021
1 parent ff0d05f commit 69289d7
Show file tree
Hide file tree
Showing 18 changed files with 1,411 additions and 1,045 deletions.
Expand Up @@ -269,10 +269,38 @@ void AddEnvironment ()
throw new InvalidOperationException ($"Unsupported BoundExceptionType value '{BoundExceptionType}'");
}

int assemblyNameWidth = 0;
int assemblyCount = ResolvedAssemblies.Length;
Encoding assemblyNameEncoding = Encoding.UTF8;

Action<ITaskItem> updateNameWidth = (ITaskItem assembly) => {
string assemblyName = Path.GetFileName (assembly.ItemSpec);
int nameBytes = assemblyNameEncoding.GetBytes (assemblyName).Length;
if (nameBytes > assemblyNameWidth) {
assemblyNameWidth = nameBytes;
}
};

if (SatelliteAssemblies != null) {
assemblyCount += SatelliteAssemblies.Length;

foreach (ITaskItem assembly in SatelliteAssemblies) {
updateNameWidth (assembly);
}
}

foreach (var assembly in ResolvedAssemblies) {
updateNameWidth (assembly);
}

int abiNameLength = 0;
foreach (string abi in SupportedAbis) {
if (abi.Length <= abiNameLength) {
continue;
}
abiNameLength = abi.Length;
}
assemblyNameWidth += abiNameLength + 1; // room for '/'

bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath);
var appConfState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ApplicationConfigTaskState> (ApplicationConfigTaskState.RegisterTaskObjectKey, RegisteredTaskObjectLifetime.Build);
Expand All @@ -295,6 +323,7 @@ void AddEnvironment ()
JniAddNativeMethodRegistrationAttributePresent = appConfState != null ? appConfState.JniAddNativeMethodRegistrationAttributePresent : false,
HaveRuntimeConfigBlob = haveRuntimeConfigBlob,
NumberOfAssembliesInApk = assemblyCount,
BundledAssemblyNameWidth = assemblyNameWidth + 1,
};

using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
Expand Down
Expand Up @@ -31,9 +31,10 @@ public sealed class ApplicationConfig
public uint environment_variable_count;
public uint system_property_count;
public uint number_of_assemblies_in_apk;
public uint bundled_assembly_name_width;
public string android_package_name;
};
const uint ApplicationConfigFieldCount = 14;
const uint ApplicationConfigFieldCount = 15;

static readonly object ndkInitLock = new object ();
static readonly char[] readElfFieldSeparator = new [] { ' ', '\t' };
Expand Down Expand Up @@ -182,7 +183,12 @@ static ApplicationConfig ReadApplicationConfig (string envFile)
ret.number_of_assemblies_in_apk = ConvertFieldToUInt32 ("number_of_assemblies_in_apk", envFile, i, field [1]);
break;

case 13: // android_package_name: string / [pointer type]
case 13: // bundled_assembly_name_width: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}");
ret.bundled_assembly_name_width = ConvertFieldToUInt32 ("bundled_assembly_name_width", envFile, i, field [1]);
break;

case 14: // android_package_name: string / [pointer type]
Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile}:{i}': {field [0]}");
pointers.Add (field [1].Trim ());
break;
Expand Down
Expand Up @@ -8,166 +8,166 @@
"Size": 7236
},
"assemblies/Java.Interop.dll": {
"Size": 64941
"Size": 64940
},
"assemblies/Microsoft.Win32.Primitives.dll": {
"Size": 4856
"Size": 4869
},
"assemblies/Mono.Android.dll": {
"Size": 453236
"Size": 453210
},
"assemblies/mscorlib.dll": {
"Size": 4216
"Size": 4223
},
"assemblies/netstandard.dll": {
"Size": 5923
"Size": 5931
},
"assemblies/rc.bin": {
"Size": 946
},
"assemblies/System.Collections.Concurrent.dll": {
"Size": 12647
"Size": 12657
},
"assemblies/System.Collections.dll": {
"Size": 20436
"Size": 20542
},
"assemblies/System.Collections.NonGeneric.dll": {
"Size": 9490
"Size": 9513
},
"assemblies/System.ComponentModel.dll": {
"Size": 2907
"Size": 2916
},
"assemblies/System.ComponentModel.Primitives.dll": {
"Size": 3500
"Size": 3509
},
"assemblies/System.ComponentModel.TypeConverter.dll": {
"Size": 7237
"Size": 7245
},
"assemblies/System.Console.dll": {
"Size": 6854
"Size": 6865
},
"assemblies/System.Core.dll": {
"Size": 2409
"Size": 2416
},
"assemblies/System.Diagnostics.DiagnosticSource.dll": {
"Size": 4029
"Size": 4039
},
"assemblies/System.Diagnostics.TraceSource.dll": {
"Size": 7708
"Size": 7718
},
"assemblies/System.dll": {
"Size": 2772
"Size": 2779
},
"assemblies/System.Drawing.dll": {
"Size": 2462
"Size": 2471
},
"assemblies/System.Drawing.Primitives.dll": {
"Size": 13157
"Size": 13166
},
"assemblies/System.Formats.Asn1.dll": {
"Size": 28056
"Size": 28064
},
"assemblies/System.IO.Compression.Brotli.dll": {
"Size": 12614
"Size": 12625
},
"assemblies/System.IO.Compression.dll": {
"Size": 20019
"Size": 20029
},
"assemblies/System.IO.IsolatedStorage.dll": {
"Size": 12048
"Size": 12056
},
"assemblies/System.Linq.dll": {
"Size": 21047
"Size": 21059
},
"assemblies/System.Linq.Expressions.dll": {
"Size": 186847
"Size": 187198
},
"assemblies/System.Net.Http.dll": {
"Size": 219177
"Size": 219143
},
"assemblies/System.Net.NameResolution.dll": {
"Size": 14139
"Size": 14148
},
"assemblies/System.Net.NetworkInformation.dll": {
"Size": 18709
"Size": 18719
},
"assemblies/System.Net.Primitives.dll": {
"Size": 43138
"Size": 43113
},
"assemblies/System.Net.Quic.dll": {
"Size": 47303
"Size": 47792
},
"assemblies/System.Net.Requests.dll": {
"Size": 4372
"Size": 4382
},
"assemblies/System.Net.Security.dll": {
"Size": 59085
"Size": 59106
},
"assemblies/System.Net.Sockets.dll": {
"Size": 56023
"Size": 56026
},
"assemblies/System.ObjectModel.dll": {
"Size": 13202
"Size": 13222
},
"assemblies/System.Private.CoreLib.dll": {
"Size": 807464
"Size": 807755
},
"assemblies/System.Private.DataContractSerialization.dll": {
"Size": 198721
"Size": 198766
},
"assemblies/System.Private.Uri.dll": {
"Size": 45114
"Size": 45147
},
"assemblies/System.Private.Xml.dll": {
"Size": 256175
"Size": 256185
},
"assemblies/System.Private.Xml.Linq.dll": {
"Size": 18743
"Size": 18752
},
"assemblies/System.Runtime.CompilerServices.Unsafe.dll": {
"Size": 1844
},
"assemblies/System.Runtime.dll": {
"Size": 2868
"Size": 2877
},
"assemblies/System.Runtime.InteropServices.RuntimeInformation.dll": {
"Size": 3934
"Size": 3945
},
"assemblies/System.Runtime.Numerics.dll": {
"Size": 25308
"Size": 25253
},
"assemblies/System.Runtime.Serialization.dll": {
"Size": 2384
"Size": 2392
},
"assemblies/System.Runtime.Serialization.Formatters.dll": {
"Size": 3791
"Size": 3802
},
"assemblies/System.Runtime.Serialization.Primitives.dll": {
"Size": 4804
"Size": 4813
},
"assemblies/System.Security.Cryptography.Algorithms.dll": {
"Size": 44515
"Size": 44388
},
"assemblies/System.Security.Cryptography.Encoding.dll": {
"Size": 15178
"Size": 15181
},
"assemblies/System.Security.Cryptography.Primitives.dll": {
"Size": 10308
"Size": 10319
},
"assemblies/System.Security.Cryptography.X509Certificates.dll": {
"Size": 78809
"Size": 78796
},
"assemblies/System.Text.RegularExpressions.dll": {
"Size": 78319
"Size": 78340
},
"assemblies/System.Threading.Channels.dll": {
"Size": 17982
"Size": 18006
},
"assemblies/System.Xml.dll": {
"Size": 2278
"Size": 2285
},
"assemblies/UnnamedProject.dll": {
"Size": 117076
"Size": 117083
},
"assemblies/Xamarin.AndroidX.Activity.dll": {
"Size": 6374
Expand Down Expand Up @@ -239,10 +239,10 @@
"Size": 3455384
},
"lib/arm64-v8a/libmonodroid.so": {
"Size": 338784
"Size": 342824
},
"lib/arm64-v8a/libmonosgen-2.0.so": {
"Size": 3155488
"Size": 3159584
},
"lib/arm64-v8a/libSystem.IO.Compression.Native.so": {
"Size": 776216
Expand All @@ -254,7 +254,7 @@
"Size": 150024
},
"lib/arm64-v8a/libxamarin-app.so": {
"Size": 126816
"Size": 134032
},
"META-INF/android.support.design_material.version": {
"Size": 12
Expand Down Expand Up @@ -2006,5 +2006,5 @@
"Size": 341040
}
},
"PackageSize": 8672957
"PackageSize": 8677053
}

0 comments on commit 69289d7

Please sign in to comment.