CLR Inside out the Performance Benefits of Ngen. Surupa Biswas T Ypically, Methods in Managed Executables Are Just-In-Time (JIT)
Total Page:16
File Type:pdf, Size:1020Kb
CLR Inside Out: The Performance Benefits of NGen. Page 1 of 10 CLR Inside Out The Performance Benefits of NGen. Surupa Biswas Contents Why Use NGen? Using NGen Assemblies and the GAC Choosing Base Addresses Avoid Rebasing NGen and Multiple AppDomains Using Hardbinding Regenerating NGen Images Final Notes T ypically, methods in managed executables are just-in-time (JIT) compiled. The machine code generated by the JIT compiler is thrown away once the process running that executable exits; therefore, the method must be recompiled when the application is run again. Moreover, the generated code is tied to the process that created it and cannot be shared between processes that are running the same application. These characteristics of .NET JIT compilation can have performance drawbacks. Fortunately, NGen (native image generation) offers some relief. NGen refers to the process of precompiling Microsoft® intermediate language (MSIL) executables into machine code prior to execution time. This results in two primary performance benefits. First, it reduces application startup time by avoiding the need to compile code at run time. Second, it improves memory usage by allowing for code pages to be shared across multiple processes. Although at first glance NGen appears to be similar to traditional static back-end compilation, it is actually quite different. Unlike statically compiled binaries, NGen images are tied to the machine where they are created and hence cannot be deployed. Instead, the application's installer needs to issue commands to create native images for the specific assemblies on the client machine at setup time. Also unlike traditional binaries, NGen images merely form a cache-managed applications will continue to run correctly even if all the NGen images are deleted. Of course, there will be a performance hit if that happens, but there will be no correctness issues. On the other hand, managed applications may fail to run correctly if the MSIL assemblies are deleted once they have been compiled into NGen images. Why Use NGen? NGen typically improves the warm startup time of applications, and sometimes the cold startup time as well. Cold startup time refers to the time taken to start an application soon after a machine reboot, such that the application is being launched for the first time after that reboot. Warm startup time, on the other hand, is the time taken to start the application given that it was launched in the recent past (and there were no reboots in between). Cold startup time is primarily dominated by the number of pages that need to be fetched from disk. The improvement in cold startup time while using NGen can be attributed largely to the fact that pages of MSIL that need to be touched during compilation no longer need to be accessed at execution time. mk:@MSITStore:C:\INetSave\MSDNMagazineMay2006en-us.chm::/CLRInsideOut/... 06.10.2017 CLR Inside Out: The Performance Benefits of NGen. Page 2 of 10 Improvements in warm startup time come from reusing pages of the NGen images that were brought in when the application had been running earlier. This is especially beneficial to large client-side UI applications where startup time is critical to the user experience. NGen also improves the overall memory usage of the system by allowing different processes that use the same assembly to share the corresponding NGen image among them, as shown in Figure 1. This can be very useful in both client and server scenarios in which the total memory footprint must be minimized. A classic example is the Terminal Services scenario in which a large number of users might be logged in and running the same application at the same time. If you're building libraries or other reusable components, you may also want to use NGen so that applications using your components can share the generated code pages. It is important to note that NGen does not always improve the cold startup time of applications, since NGen images are larger than MSIL assemblies. The only way to determine whether cold startup time and working set will improve or degrade with NGen for specific scenarios is to actually measure them. (More on performance measurement later.) Using NGen Figure 1 Sharing Code Among Processes You can precompile your application with NGen by using the ngen.exe tool that ships with the Microsoft .NET Framework redistributable. The .NET Framework 2.0 has a completely reworked version of this tool and also has a new NGen service called the .NET Runtime Optimization service, which is capable of NGen-compiling assemblies in the background. Thus, NGen now supports both synchronous and asynchronous compilation. The complete syntax for ngen.exe can be found at .NET Framework Tools. All NGen images are installed in the native image cache, which is located in the %WINDIR% \assembly directory. For example, "NGen install foo.exe" will create a native image called foo.ni.exe and install it in the native image cache. Figure 2 shows the location of the native image for System.Web.dll. Figure 2 Native Image Cache Using the NGen tool you can choose to compile your assemblies either synchronously (for example, "NGen install foo.exe") or asynchronously with one of three priority levels (for example, "NGen install foo.exe /queue:3"). The asynchronous commands return immediately and are processed in the background by the NGen service. Assemblies queued up with priority 1 or 2 are compiled as soon as possible by the NGen service (priority 1 assemblies are compiled before priority 2 assemblies). Priority 3 assemblies are compiled by the service when it detects that the machine is idle. You can also use the executeQueuedItems command-line switch to force all queued items to be executed, as mk:@MSITStore:C:\INetSave\MSDNMagazineMay2006en-us.chm::/CLRInsideOut/... 06.10.2017 CLR Inside Out: The Performance Benefits of NGen. Page 3 of 10 shown in Figure 3. Figure 3 Asynchronous NGen Compilation Using synchronous NGen at install time guarantees that end users will never launch your application without all the assemblies having been precompiled by NGen. However, this causes setup to take longer because the compilation time is now added to the overall install time. As an alternative, the application's setup program can issue asynchronous NGen commands. Using asynchronous NGen with priority 1 or 2 ensures that the native images will be available soon after setup completes. There is, however, a risk of the compilation work adversely affecting the performance of other applications or the system's response time to user inputs. The other option is to use asynchronous NGen with priority 3. Priority 3 compilation seems like it's "free" in the sense that it neither consumes machine cycles during setup nor disrupts system performance. However, since machines may not be idle for extended periods of time immediately after the install, the end user could launch and use the application before the NGen service gets an opportunity to compile the assemblies. Therefore you can finely tune this trade-off by dividing your application's assemblies into categories and precompiling each of these differently. Assemblies that are absolutely critical to startup time can be compiled synchronously, those that are important but not critical can be compiled by the service as priority 1 or 2, and those in less frequently used execution paths can be compiled by the service as priority 3. Assemblies and the GAC In order to get the optimal performance from NGen, all assemblies in the application need to be strong-named and installed into the Global Assembly Cache (GAC) prior to compilation. If an assembly is strong-named and not installed into the GAC, the loader will need to verify the assembly's signature at load time. Validating the strong-name signature requires the loader to parse the entire contents of the MSIL assembly, compute a hash, and then compare it to the assembly's signature. The verification process therefore fetches every page of the MSIL assembly from disk. This results in a spike in the application's working set, increases load time, and pollutes the OS disk cache. If the assembly were installed in the GAC, however, the loader would not need to validate the strong-name signature at load time. This is because the signature of the assembly is verified at the time it is installed into the GAC. The loader assumes that since the GAC is a secure store that can only be modified by an administrator, and since the signature was verified at GAC installation time, it can skip revalidating the contents. When the loader attempts to load a pure MSIL assembly from the GAC and discovers a matching NGen image, it directly loads the NGen image. In that scenario, mk:@MSITStore:C:\INetSave\MSDNMagazineMay2006en-us.chm::/CLRInsideOut/... 06.10.2017 CLR Inside Out: The Performance Benefits of NGen. Page 4 of 10 not even a single page of the MSIL assembly needs to be fetched into memory. This difference in behavior usually causes a significant difference in the startup time of NGen- compiled applications. Unless you have a reason to not want to install the assemblies into the GAC, it is highly recommended that you do so in order to get the best performance from NGen. Of course, the cost of validating the strong-name signature at run time in the non-GAC case is incurred regardless of whether the application is JIT-compiled or NGen-compiled. In the NGen case, it is just particularly painful since some or all of the MSIL pages that are brought into memory during signature verification may not be used during execution. Information on how to sign an assembly and then install it into the GAC can be found at "How to install an assembly into the Global Assembly Cache in Visual C#".