Reducing apps startup time with Pre-JITing and NGEN on a Surface RT
TL;DR: The JIT can take over a third of the startup time of a managed Metro App, and using Native Image Generation (NGEN) can greatly improve the startup time of these apps. There is also a way to check for these native images to act accordingly.
A while back, I’ve had the chance to work with the guys that are behind the Pre-JIT feature of the CLR 4.5 for Metro Apps. Back then, I was only able to work on x86/x64 architectures, as ARM/Windows RT devices were not available.
Now that the Surface RT devices are available, we’re facing quite a few challenges in terms of code execution performance, and I’m going to discuss a few tips and tricks about the Managed Code JIT on Windows RT.
Profiling a slow starting app on a Surface RT
Running apps on the Surface can be troubling. Having an app that is useable after 16 to 18 seconds is definitely not acceptable, let alone the fact that the Splash Screen can disappear after 6 to 8 seconds.
Profiling such an app that starts slowly is very interesting, when looking a the Visual Studio profiler, where during these 17 seconds, about a third is spent in a “clr.dll” module in exclusive time (time spent only in this module and not its descendants). This is a very big number.
This time is actually spent in the JIT, where big methods tend to take more time to be JITed, sometimes on the UI thread, making the app sluggish.
[more]
This JIT phase is expected though, and is since .NET 1.0. Microsoft chose not to JIT metro apps on installation supposedly to improve the user's interaction flow with apps, and did not choose to make it in the cloud like the Windows Phone team did, starting from Windows Phone 8. The drawback of this approach is that managed code apps do have an extremely poor first launch experience, where the JIT kicks in every time.
The good news is that there are workaround for this slow startup, Pre-JIT and NGEN.
Pre-JITing applications
Pre-JIT is based on profiling how an app JITs code, then creating a file out of it that can be bundled with the final app package. In later startups of the app, that file is used of JIT code ahead of time, substantially improving the app’s startup time.
Yet, this technique is not the panacea, because it still requires the CPU to JIT code every time, preventing the rest of the app to use that CPU-time for some other useful tasks.
While this profile technique works like a charm on Windows 8 desktop, I’ve yet to notice any difference in the startup time of apps on Windows RT. I’m not sure if it’s the CPU that’s not powerful enough to JIT ahead of the normal execution path, or if it’s simply not used at all.
Interestingly, I’ve been able to generate that profile on a Surface device, but that’s pretty much it.
NGEN on Windows RT
Fortunately, apps are NGENed roughly after 24 hours. This technique, which has been around since .NET 1.0, has the ability to take managed code and generate a “native” image which is basically a fully JITed assembly that targets the current CPU architecture.
That same app that takes 17 seconds to start and be useable drops down to 12 seconds. This makes the startup a lot more interesting! (12 seconds is still not acceptable, but using other optimizations like using the 4 logical cores, that same app is now down to 8 second, which is a lot better)
This NGEN tool is ran as part of a “Maintenance Task”, located in the standard Windows Scheduler. It can be found when searching for “Schedule tasks” in the start menu, then under “Microsoft / Windows / .NET Framework”. Right clicking and selecting Run for both “.NET Framework NGEN v4.0.30319” and “.NET Framework NGEN v4.0.30319 64” will generate native images for applications that have been run at least once for what seems to be about 10 to 15 seconds.
On the Surface RT, generating these images can take quite some time depending on the size of the app. I’ve seen apps that take about 30 seconds to a minute to fully generate. Also note that there are space constraints, such as available free space. If your system drive has limited free space (such as below 1GB), NGEN will not generate anything.
Microsoft recommends running this tool by hand using a command line, but I’ve yet to see it work properly on a Surface RT device… Only running the scheduled task manually actually generates native images.
Checking for native images
Knowing that you’re running with or without native images can allow you to act differently to mitigate the terrible initial startup time.
The generated native images are located under the following folder:
%LOCALAPPDATA%\Packages\[AppID]\AC\Microsoft\CLR_v4.0_32\NativeImages
Testing for its presence is enough to guess it the app is running without JIT.
The following code can tell you this :
private async Task IsNativeImage() { var source = Windows.Storage.ApplicationData.Current.LocalFolder.Path + "\\..\\AC\\Microsoft\\"; try { await StorageFolder.GetFolderFromPathAsync(source + "CLR_v4.0_32\\NativeImages"); return true; } catch (Exception ex) { } try { await StorageFolder.GetFolderFromPathAsync(source + "CLR_v4.0\\NativeImages"); return true; } catch (Exception ex) { } return false; }
So when testing for your app’s performance, don’t forget to manually run NGEN and create a JIT profile !