Skip to content

Commit 2866832

Browse files
authored
[monodroid] Use more standard C++ features, performance improvements (dotnet#6159)
C++ features: * Use C++20 concepts to improve compile-time correctness verification * Use `std::vector` for dynamic heap buffers * Use `std::array` for static stack buffers * Use `std::unique_ptr` instead of the home-grown equivalent (also for C-allocated memory) * Use C++20 structure designators Optimizations: * Add string routines (`ends_with()` and `find_last()`) optimized for the stack string types (derived from `string_base`) * Count assemblies at build time and pre-allocate enough memory (in the form of a `std::vector` instance) to store information about the bundled assemblies, thus avoiding 2 allocations per assembly (one `realloc()` and one `malloc()`) making the bundled assemblies loading algorithm closer to O(1) instead of the previous O(n*2). * Use character arrays instead of "plain" string literals to optimize some string operations. These changes improve plain XA test app startup time by around 11% Additionally, rename `string_segment::append(const char*)` to `string_segment::append_c(const char*)`. We [found that][0] the C++ compiler preferred the non-`template` `append(const char*)` overload over the `template<size_t S> append(const char (&s)[S])` overload, meaning `append("foo")` would hit the slower `strlen()`-based method. Likewise create `assign_c(const char*)` and `starts_with_c(const char*)` member functions. Finally, [upon review][1] of the exported symbols, we found that many symbols that we didn't want to export were being exported: % $HOME/android-toolchain/ndk/toolchains/llvm/prebuilt/darwin-x86_64/bin/x86_64-linux-android-nm -D -C bin/Debug/lib/xamarin.android/xbuild/Xamarin/Android/lib/x86_64/libmono-android.debug.so … 000000000003e480 T std::terminate() 0000000000014f80 T operator delete[](void*) 0000000000014f90 T operator delete[](void*, std::nothrow_t const&) 0000000000014fa0 T operator delete[](void*, unsigned long) 0000000000014f50 T operator delete(void*) 0000000000014f60 T operator delete(void*, std::nothrow_t const&) 0000000000014f70 T operator delete(void*, unsigned long) 0000000000014ef0 T operator new[](unsigned long) 0000000000014f30 T operator new[](unsigned long, std::nothrow_t const&) 0000000000014e90 T operator new(unsigned long) 0000000000014ed0 T operator new(unsigned long, std::nothrow_t const&) Update our declarations of these functions so that they are instead *weak* symbols, or otherwise no longer exported: 0000000000011e10 W operator delete[](void*) 0000000000011dfc W operator delete(void*) 0000000000011dec W operator new[](unsigned long) 0000000000011da8 W operator new(unsigned long) [0]: dotnet#6159 (comment) [1]: dotnet#6159 (comment)
1 parent cf2a39b commit 2866832

23 files changed

+485
-445
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class GeneratePackageManagerJava : AndroidTask
2828
[Required]
2929
public ITaskItem[] ResolvedUserAssemblies { get; set; }
3030

31+
public ITaskItem[] SatelliteAssemblies { get; set; }
32+
3133
[Required]
3234
public string OutputDirectory { get; set; }
3335

@@ -264,6 +266,11 @@ void AddEnvironment ()
264266
throw new InvalidOperationException ($"Unsupported BoundExceptionType value '{BoundExceptionType}'");
265267
}
266268

269+
int assemblyCount = ResolvedAssemblies.Length;
270+
if (SatelliteAssemblies != null) {
271+
assemblyCount += SatelliteAssemblies.Length;
272+
}
273+
267274
bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath);
268275
var appConfState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ApplicationConfigTaskState> (ApplicationConfigTaskState.RegisterTaskObjectKey, RegisteredTaskObjectLifetime.Build);
269276
foreach (string abi in SupportedAbis) {
@@ -284,6 +291,7 @@ void AddEnvironment ()
284291
InstantRunEnabled = InstantRunEnabled,
285292
JniAddNativeMethodRegistrationAttributePresent = appConfState != null ? appConfState.JniAddNativeMethodRegistrationAttributePresent : false,
286293
HaveRuntimeConfigBlob = haveRuntimeConfigBlob,
294+
NumberOfAssembliesInApk = assemblyCount,
287295
};
288296

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

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ public sealed class ApplicationConfig
3030
public uint package_naming_policy;
3131
public uint environment_variable_count;
3232
public uint system_property_count;
33+
public uint number_of_assemblies_in_apk;
3334
public string android_package_name;
3435
};
35-
const uint ApplicationConfigFieldCount = 13;
36+
const uint ApplicationConfigFieldCount = 14;
3637

3738
static readonly object ndkInitLock = new object ();
3839
static readonly char[] readElfFieldSeparator = new [] { ' ', '\t' };
@@ -176,7 +177,12 @@ static ApplicationConfig ReadApplicationConfig (string envFile)
176177
ret.system_property_count = ConvertFieldToUInt32 ("system_property_count", envFile, i, field [1]);
177178
break;
178179

179-
case 12: // android_package_name: string / [pointer type]
180+
case 12: // number_of_assemblies_in_apk: uint32_t / .word | .long
181+
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}");
182+
ret.number_of_assemblies_in_apk = ConvertFieldToUInt32 ("number_of_assemblies_in_apk", envFile, i, field [1]);
183+
break;
184+
185+
case 13: // android_package_name: string / [pointer type]
180186
Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile}:{i}': {field [0]}");
181187
pointers.Add (field [1].Trim ());
182188
break;

src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class ApplicationConfigNativeAssemblyGenerator : NativeAssemblyGenerator
2323
public bool InstantRunEnabled { get; set; }
2424
public bool JniAddNativeMethodRegistrationAttributePresent { get; set; }
2525
public bool HaveRuntimeConfigBlob { get; set; }
26+
public int NumberOfAssembliesInApk { get; set; }
2627

2728
public PackageNamingPolicy PackageNamingPolicy { get; set; }
2829

@@ -86,6 +87,9 @@ protected override void WriteSymbols (StreamWriter output)
8687
WriteCommentLine (output, "system_property_count");
8788
size += WriteData (output, systemProperties == null ? 0 : systemProperties.Count * 2);
8889

90+
WriteCommentLine (output, "number_of_assemblies_in_apk");
91+
size += WriteData (output, NumberOfAssembliesInApk);
92+
8993
WriteCommentLine (output, "android_package_name");
9094
size += WritePointer (output, MakeLocalLabel (stringLabel));
9195

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,6 +1574,7 @@ because xbuild doesn't support framework reference assemblies.
15741574
<GeneratePackageManagerJava
15751575
ResolvedAssemblies="@(_ResolvedAssemblies)"
15761576
ResolvedUserAssemblies="@(_ResolvedUserAssemblies)"
1577+
SatelliteAssemblies="@(_AndroidResolvedSatellitePaths)"
15771578
MainAssembly="$(TargetPath)"
15781579
OutputDirectory="$(_AndroidIntermediateJavaSourceDirectory)mono"
15791580
EnvironmentOutputDirectory="$(IntermediateOutputPath)android"

src/monodroid/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ if(ANDROID)
492492
${BIONIC_SOURCES_DIR}/cxa_guard.cc
493493
${SOURCES_DIR}/cxx-abi/string.cc
494494
${SOURCES_DIR}/cxx-abi/terminate.cc
495+
${SOURCES_DIR}/cxx-abi/vector.cc
495496
)
496497
endif()
497498
else()

src/monodroid/jni/android-system.cc

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,9 @@ AndroidSystem::monodroid_get_system_property_from_overrides ([[maybe_unused]] co
307307
#if defined (DEBUG) || !defined (ANDROID)
308308
for (size_t oi = 0; oi < MAX_OVERRIDES; ++oi) {
309309
if (override_dirs [oi]) {
310-
simple_pointer_guard<char[]> override_file (utils.path_combine (override_dirs [oi], name));
310+
std::unique_ptr<char[]> override_file {utils.path_combine (override_dirs [oi], name)};
311311
log_info (LOG_DEFAULT, "Trying to get property from %s", override_file.get ());
312-
size_t result = _monodroid_get_system_property_from_file (override_file, value);
312+
size_t result = _monodroid_get_system_property_from_file (override_file.get (), value);
313313
if (result == 0 || value == nullptr || (*value) == nullptr || **value == '\0') {
314314
continue;
315315
}
@@ -351,9 +351,9 @@ AndroidSystem::get_full_dso_path (const char *base_dir, const char *dso_path, dy
351351
if (base_dir == nullptr || utils.is_path_rooted (dso_path))
352352
return const_cast<char*>(dso_path); // Absolute path or no base path, can't do much with it
353353

354-
path.assign (base_dir)
354+
path.assign_c (base_dir)
355355
.append (MONODROID_PATH_SEPARATOR)
356-
.append (dso_path);
356+
.append_c (dso_path);
357357

358358
return true;
359359
}
@@ -567,7 +567,7 @@ AndroidSystem::setup_environment_from_override_file (const char *path)
567567
auto file_size = static_cast<size_t>(sbuf.st_size);
568568
size_t nread = 0;
569569
ssize_t r;
570-
simple_pointer_guard<char[]> buf (new char [file_size]);
570+
auto buf = std::make_unique<char[]> (file_size);
571571

572572
do {
573573
auto read_count = static_cast<read_count_type>(file_size - nread);
@@ -603,14 +603,14 @@ AndroidSystem::setup_environment_from_override_file (const char *path)
603603
}
604604

605605
char *endptr;
606-
unsigned long name_width = strtoul (buf, &endptr, 16);
607-
if ((name_width == std::numeric_limits<unsigned long>::max () && errno == ERANGE) || (*buf != '\0' && *endptr != '\0')) {
606+
unsigned long name_width = strtoul (buf.get (), &endptr, 16);
607+
if ((name_width == std::numeric_limits<unsigned long>::max () && errno == ERANGE) || (buf[0] != '\0' && *endptr != '\0')) {
608608
log_warn (LOG_DEFAULT, "Malformed header of the environment override file %s: name width has invalid format", path);
609609
return;
610610
}
611611

612612
unsigned long value_width = strtoul (buf.get () + 11, &endptr, 16);
613-
if ((value_width == std::numeric_limits<unsigned long>::max () && errno == ERANGE) || (*buf != '\0' && *endptr != '\0')) {
613+
if ((value_width == std::numeric_limits<unsigned long>::max () && errno == ERANGE) || (buf[0] != '\0' && *endptr != '\0')) {
614614
log_warn (LOG_DEFAULT, "Malformed header of the environment override file %s: value width has invalid format", path);
615615
return;
616616
}
@@ -625,7 +625,7 @@ AndroidSystem::setup_environment_from_override_file (const char *path)
625625
char *name = buf.get () + OVERRIDE_ENVIRONMENT_FILE_HEADER_SIZE;
626626
while (data_size > 0 && data_size >= data_width) {
627627
if (*name == '\0') {
628-
log_warn (LOG_DEFAULT, "Malformed environment override file %s: name at offset %lu is empty", path, name - buf);
628+
log_warn (LOG_DEFAULT, "Malformed environment override file %s: name at offset %lu is empty", path, name - buf.get ());
629629
return;
630630
}
631631

@@ -707,9 +707,9 @@ AndroidSystem::setup_environment ()
707707
#if defined (DEBUG) || !defined (ANDROID)
708708
// TODO: for debug read from file in the override directory named `environment`
709709
for (size_t oi = 0; oi < MAX_OVERRIDES; oi++) {
710-
simple_pointer_guard<char[]> env_override_file (utils.path_combine (override_dirs [oi], OVERRIDE_ENVIRONMENT_FILE_NAME));
711-
if (utils.file_exists (env_override_file)) {
712-
setup_environment_from_override_file (env_override_file);
710+
std::unique_ptr<char[]> env_override_file {utils.path_combine (override_dirs [oi], OVERRIDE_ENVIRONMENT_FILE_NAME)};
711+
if (utils.file_exists (env_override_file.get ())) {
712+
setup_environment_from_override_file (env_override_file.get ());
713713
}
714714
}
715715
#endif

src/monodroid/jni/application_dso_stub.cc

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,20 @@ CompressedAssemblies compressed_assemblies = {
3535
};
3636

3737
ApplicationConfig application_config = {
38-
/*.uses_mono_llvm =*/ false,
39-
/*.uses_mono_aot =*/ false,
40-
/*.uses_assembly_preload =*/ false,
41-
/*.is_a_bundled_app =*/ false,
42-
/*.broken_exception_transitions =*/ false,
43-
/*.instant_run_enabled =*/ false,
44-
/*.jni_add_native_method_registration_attribute_present =*/ false,
45-
/*.have_runtime_config_blob =*/ false,
46-
/*.bound_exception_type =*/ 0, // System
47-
/*.package_naming_policy =*/ 0,
48-
/*.environment_variable_count =*/ 0,
49-
/*.system_property_count =*/ 0,
50-
/*.android_package_name =*/ "com.xamarin.test",
38+
.uses_mono_llvm = false,
39+
.uses_mono_aot = false,
40+
.uses_assembly_preload = false,
41+
.is_a_bundled_app = false,
42+
.broken_exception_transitions = false,
43+
.instant_run_enabled = false,
44+
.jni_add_native_method_registration_attribute_present = false,
45+
.have_runtime_config_blob = false,
46+
.bound_exception_type = 0, // System
47+
.package_naming_policy = 0,
48+
.environment_variable_count = 0,
49+
.system_property_count = 0,
50+
.number_of_assemblies_in_apk = 0,
51+
.android_package_name = "com.xamarin.test",
5152
};
5253

5354
const char* mono_aot_mode_name = "";

src/monodroid/jni/basic-android-system.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ void
1616
BasicAndroidSystem::detect_embedded_dso_mode (jstring_array_wrapper& appDirs) noexcept
1717
{
1818
// appDirs[2] points to the native library directory
19-
simple_pointer_guard<char[]> libmonodroid_path = utils.path_combine (appDirs[2].get_cstr (), "libmonodroid.so");
19+
std::unique_ptr<char> libmonodroid_path {utils.path_combine (appDirs[2].get_cstr (), "libmonodroid.so")};
2020
log_debug (LOG_ASSEMBLY, "Checking if libmonodroid was unpacked to %s", libmonodroid_path.get ());
21-
if (!utils.file_exists (libmonodroid_path)) {
21+
if (!utils.file_exists (libmonodroid_path.get ())) {
2222
log_debug (LOG_ASSEMBLY, "%s not found, assuming application/android:extractNativeLibs == false", libmonodroid_path.get ());
2323
set_embedded_dso_mode_enabled (true);
2424
} else {

src/monodroid/jni/basic-utilities.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@ BasicUtilities::create_directory (const char *pathname, mode_t mode)
6565
mode_t oldumask;
6666
#endif
6767
oldumask = umask (022);
68-
simple_pointer_guard<char[]> path (strdup_new (pathname));
68+
std::unique_ptr<char> path {strdup_new (pathname)};
6969
int rv, ret = 0;
70-
for (char *d = path; d != nullptr && *d; ++d) {
70+
for (char *d = path.get (); d != nullptr && *d; ++d) {
7171
if (*d != '/')
7272
continue;
7373
*d = 0;
7474
if (*path) {
75-
rv = make_directory (path, mode);
75+
rv = make_directory (path.get (), mode);
7676
if (rv == -1 && errno != EEXIST) {
7777
ret = -1;
7878
break;

src/monodroid/jni/basic-utilities.hh

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,17 @@ namespace xamarin::android
5858
abort_unless (path1 != nullptr || path2 != nullptr, "At least one path must be a valid pointer");
5959

6060
if (path1 == nullptr) {
61-
buf.append (path2);
61+
buf.append_c (path2);
6262
return;
6363
}
6464

6565
if (path2 == nullptr) {
66-
buf.append (path1);
66+
buf.append_c (path1);
6767
return;
6868
}
6969

7070
buf.append (path1, path1_len);
71-
buf.append (MONODROID_PATH_SEPARATOR, MONODROID_PATH_SEPARATOR_LENGTH);
71+
buf.append (MONODROID_PATH_SEPARATOR);
7272
buf.append (path2, path2_len);
7373
}
7474

@@ -109,6 +109,35 @@ namespace xamarin::android
109109
return p != nullptr && p [N - 1] == '\0';
110110
}
111111

112+
template<size_t N, size_t MaxStackSize, typename TStorage, typename TChar = char>
113+
bool ends_with (internal::string_base<MaxStackSize, TStorage, TChar> const& str, const char (&end)[N]) const noexcept
114+
{
115+
constexpr size_t end_length = N - 1;
116+
117+
size_t len = str.length ();
118+
if (XA_UNLIKELY (len < end_length)) {
119+
return false;
120+
}
121+
122+
return memcmp (str.get () + len - end_length, end, end_length) == 0;
123+
}
124+
125+
template<size_t MaxStackSize, typename TStorage, typename TChar = char>
126+
const TChar* find_last (internal::string_base<MaxStackSize, TStorage, TChar> const& str, const char ch) const noexcept
127+
{
128+
if (str.empty ()) {
129+
return nullptr;
130+
}
131+
132+
for (size_t i = str.length () - 1; i >= 0; i--) {
133+
if (str[i] == ch) {
134+
return str.get () + i;
135+
}
136+
}
137+
138+
return nullptr;
139+
}
140+
112141
void *xmalloc (size_t size)
113142
{
114143
return ::xmalloc (size);

0 commit comments

Comments
 (0)