Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[monodroid] Speed up P/Invoke override lookups (#6210)
Commit 0cd890b added use of [robin_map][0] to cache P/Invokes lookups. This incurred a startup time penalty, as the map must be instantiated and populated with `std::string` instances. Continue using `robin_map` as a fallback cache, and introduce a "primary" P/Invoke lookup data structure, an array of structures describing each P/Invoke entry: struct PinvokeEntry { hash_t hash; const char *name; void *func; }; static PinvokeEntry internal_pinvokes[] = { // … }; static PinvokeEntry dotnet_pinvokes[] = … `PinvokeEntry::hash` is a hash of `PinvokeEntry::name`, computed via a modified [xxHash algorithm][1], in `src/monodroid/jni/xxhash.hh`. The `internal_pinvokes` & `dotnet_pinvokes` arrays are sorted on `PinvokeEntry::hash`, allowing a binary search to be performed after hashing the function name to find. The `internal_pinvokes` array contains P/Invokes for the `xa-internal-api` and `java-interop` library names, and are fully configured at build-time, as we can directly reference the target functions: PinvokeEntry internal_pinvokes[] = { {0x1e035ea, "java_interop_jnienv_get_string_chars", reinterpret_cast<void*>(&java_interop_jnienv_get_string_chars)}, // … }; The `dotnet_pinvokes` array *doesn't* mention the function targets: static PinvokeEntry dotnet_pinvokes[] = { {0xaf6b1c, "AndroidCryptoNative_RsaPrivateDecrypt", nullptr}, // … }; `PinvokeEntry::func` will be computed on first request for a given P/Invoke, when we load the relevant library and look up the symbol, storing the pointer in the array afterwards. Both the internal and dotnet library names are matched without using string comparison. Instead, a hash of the library name passed from Mono is generated and compared against pre-generated hashes to determine which table is to be used for lookups. Both the tables and library name hashes are generated by the new `generate-pinvoke-tables` C++ app, which also serves as a build-time test of the xxHash implementation (it includes a handful of simple compile-time tests). Alas, currently this program can be built only on the Linux CI bots, since neither Windows nor macOS machines support enough of C++20. For this reason, the generated file is committed to the repository instead of generating it each time `src/monodroid` is built. When building on CI Linux machines, we do compile the utility, generate the tables, and compare the generated file to the one committed to repository. If the files differ, the build will error out and let us examine the differences (both the new generated file and the diff are part of the build status archive). More work needs to be done in order to compare the public interfaces found in the dotnet shared libraries against the table stored in the generator. The generated tables allow us to not use the "slow" `robin_map<>` hash path during application startup (MAUI apps use the .NET 6+ P/Invokes during that phase). Additionally, the `robin_map<>` cache now uses the xxHash hashes as well for faster lookups. In addition, both the embedded assembly mmap code as well as the .NET 6+ P/Invoke entry code now use the GCC/clang `__atomic` APIs to atomically set pointers, which avoids having to use any form of locks. A new lock guard class, `StartupAwareLock`, is used to take a lock only after the startup phase of Xamarin.Android is complete and it's possible that multiple threads may be calling the runtime routines. All of the above changes improve application startup time on a Pixel 3 XL device by: * Plain XA sample: ~17ms faster * Hello MAUI sample: ~100ms faster [0]: https://github.com/Tessil/robin-map [1]: https://github.com/ekpyron/xxhashct
- Loading branch information