Supercharge Your Xamarin.Mac Application Startup Time
Startup performance matters, as it’s the first feature users come across when using your application. In this post, we’ll walk through some recent samples that improve Xamarin.Mac startup times, especially for large applications, featuring a few build options that change startup time drastically, including one that cuts startup time by 77ms or about 15%.
Let’s start with the SourceWriter, a simple but full Xamarin.Mac application sample that provides a text editor with code completion and syntax highlighting.
Launching a Debug build takes around a half a second, which isn’t bad, but is difficult to measure manually. By applying the technique from this sample and averaging five launches of the application, we can measure an average startup time of 574ms.
To determine how much overhead is related to Debug, switching to Release configuration can drop the time to 454ms, a 20% + improvement! A vast majority of this improvement is due to the Static Registrar, which you can see by adding
registrar:static to the MMP arguments (Project Options → Mac Build), giving 477ms.
To understand what the registrar settings are doing, we have to dig a bit under the hood. During startup, the Objective-C runtime needs to be informed of the various managed classes that derive from NSObject and the selectors they support. The three options for the registrar setting are Partial Static, Static, and Dynamic.
- The Partial Static registrar uses reflection on user assemblies during startup and is the default for Debug builds.
- The Static registrar scans all assemblies and generates code at build time, which is a significant performance improvement (17% in this example). In contrast, it also increases build time, which is why it’s only on by default for Release configurations.
- There’s also a Dynamic registrar which reflects overall assemblies at startup, not just users assemblies. Previous to Xamarin.Mac 3.2, when the “Partial Static” registrar was introduced, this was the default for Debug configurations.
Another job done at startup is a compilation of managed code from IL to machine code. While JIT compilation is incredibly fast in most cases, for large applications it can take up a significant percentage of startup time. Ahead of Time (AOT) compilation compiles assemblies to machine code (
dylibs) during the build and bundles them inside the final application bundle.
Enabling AOT requires passing a command line argument via the “Additional MMP arguments” field in Mac Build. Full AOT options will be added to the Mac Build panel in Xamarin.Mac 4.2. The current options are as follows:
aot[=VALUE] Specify assemblies that should be AOT compiled
- none - No AOT (default)
- all - Every assembly in MonoBundle
- core - Xamarin.Mac, System, mscorlib
- sdk - Xamarin.Mac.dll and BCL assemblies
- |hybrid after option enables hybrid AOT which
allows IL stripping but is slower (only valid
- Individual files can be included for AOT via +
FileName.dll and excluded via -FileName.dll
In the SourceWriter example,
aot:all reduces startup time to a quick 377ms. This is below the 400-500 ms goal that many users will consider “instant,” and a good place to be.
While AOT compilation is great and can provide that last push to get under the desired performance window, it does have some drawbacks to consider. In SourceWriter, it increases build time by 40% and doubles the final application bundle size (i.e. 12 megs to 24 megs).
More Build Settings
Other build settings to consider are Target Framework and Linking mode. Linking can dramatically reduce application size by removing unused code, but is only available in some Target Framework configurations. Applications optimized with Linking are great candidates for AOT compilation.
If you’re interested in Xamarin.Mac, Hello, Mac is a great place to get started. There are a number of tutorials covering macOS specific user interface controls and a wealth of samples to look at. The Xamarin.Mac forum community is also open for discussions and questions as well.