Optimizing Managed Code Execution
As an application grows ever more complex, it is necessary to build a more efficient and faster .NET application that requires a special treatment of .NET assemblies in a global assembly cache in order to attain faster execution. This article showcases how to write and execute high-performance .NET-managed code by employing the native image generator utility. NGen.exe is a remarkable tool for achieving application performance, but some of its disadvantages are also illustrated by this article; scenario guidelines to which it is best fitted are recommended.
JIT versus Native Assembly
Assemblies store code in the MSIL format. When a method is invoked for the first time, the CLR JIT (just-in-time) compiles that method into native machine code. The native code is stored in memory and directly used for any subsequent call to this method. In the JIT compilation mode, a method is slow when it is called for the first time because an additional step of compilation is involved, but any subsequent calls to that method will run as fast as native code itself.
When you view the GAC, note that some assemblies have their type marked as native image, which implies that these assemblies were precompiled in native code before they were installed in the native image cache. You can create a native image for your assembly by using the native image generator tool, which is installed as a part of the .NET framework SDK.
NGen.exe (Native Image Generation)
CLR doesn’t interpret IL codes. Rather, it uses a just-in-time (JIT) compiler to compile IL code to native machine code at run time. It is clear that the conversion of managed code to native code imposes some performance costs, during classes, library loading, and application start-up. In order to overcome such problems and to make retrieval more responsive, the CLR offers ahead-of-time JIT compilation using a technology called NGen.
The NGen.exe utility, which can be found in your framework directory, enables you to perform this ahead-of-time compilation on the client machine. The outcome of this operation is stored in a central location on the machine called the native image cache. The loader typically knows to peep here when loading an assembly or DLL that is signed with a strong name. All of the .NET Framework assemblies are NGen-manipulated during the install of the framework itself.
NGen.exe uses the same code-generation tactics that the CLR JIT uses to generate native code. The code that is generated is designed to take advantage of the underlying computer architecture. An NGen image might be unusable due to subtle differences in chip capabilities. Thus, image generation must occur on the client machine as part of install rather than being done at coding time during deployment. CLR notices this at loading time and will fall back to runtime JIT. If you apply NGen operation to your exe file, then NGen traverses your application dependencies, generates the code for each, and stores the image in the native image cache alongside your program.
In version 4.0 or 4.5 of the framework, a new NGen windows service has been integrated to take care of queuing and managing the NGen application in the background. This virtually implies that your program can install, add a request to the NGen queue, and exit installation. The NGen service will then take care of compilation asynchronously.
The NGen tool has quite a few switches to control be behavior. Running NGen.exe at the command prompt will bring up detailed usage information for the tool, as shown below:
Here is a brief summary of significant switches for operating NGen.exe:
- Install—The ngen.exe install abc.dll command will JIT compile and install the image for abc.dll and its dependencies into native image cache.
- Display—Running ngen.exe display abc.dll will show you the image status for abc.dll.
- Uninstall—You can run the ngen.exe uninstall abc.dll command to entirely remove the image from the native image cache.
- Update—Running ngen.exe update will update any native images that have been invalidated due to change in an assembly.
- Queue—Invoking ngen.exe queue [pause | status | continue] command enables you to manage the queue from the command line by pausing and continuing about its status.
Note: The Visual Studio command prompt must be run under administrative privileges in order to execute Ngen.exe
NGen.exe Advantage and Disadvantage
Since the code is compiled at install time, the CLR’s JIT compilation does not have to compile the IL code at run time, and this can improve the application’s performance. The NGen.exe tool is advantageous in the following scenarios:
- Optimizing an application’s startup time—NGen.exe surely improves the application’s startup time because the code will already be compiled into native code so that compilation doesn’t have to occur at run time.
- Reducing an application working set—Some assembly will be loaded into multiple application domains or processes simultaneously. NGen.exe can reduce the application working set because it compiles the IL to native code and save the output in a separate file. This file can be memory-mapped into multiple process address spaces simultaneously.
Despite providing a few benefits on managed code, such as garbage collection, verification, and type safety, without hitting the performance problem, there are several potential issues addressed by an NGen-manipulated file:
- Loss of intellectual property protection—It is not possible to keep the intellectual property secret by shipping NGen-manipulated files without the files containing the original IL code. At run time, the CLR requires access to the assembly metadata.
- Out of sync—When CLR loads an NGen-manipulated file, it compares a number of characteristics with the previously compiled code. If any of them don’t match, the NGen-manipulated file cannot be used and the normal JIT compiler process is used instead.
- Substandard load-time performance—Every assembly file meets standard Windows PE standards and each contains a preferred base address. When Windows loads an NGen-manipulated file, it checks to see if the file loads at its preferred base address. If it is not, Windows relocates the files, fixing all of the memory references. This process is extremely resource- and time-consuming.
- The .NET Framework 2.0 includes a version of NGen.exe that produces images that can be shared between application domains. NGen.exe is not recommended for ASP.NET versions 1.0 and 1.1 because the assemblies that NGen.exe produces cannot be shared between application domains.
Significant Guidelines for NGen.exe
- It is recommended that you measure the application’s performance with and without NGen.exe.
- Regenerate your image when you ship new versions.
- Choose an appropriate base address for an assembly.
- Scenarios in which the ability to share assemblies will best should adopt NGen.exe.
- Scenarios with limited or no sharing should not use NGen.exe.
- Do not use NGen.exe for ASP.NET versions 1.0 and 1.1. Instead, consider it for ASP.NET version 2.0.
Due to all of the issues related to NGen-manipulated files, you should be very cautious when considering the use of NGen.exe. For a client application, it might make sense to improve startup time if an assembly is used by multiple applications simultaneously. The CLR will not need to load the JIT compiler at all, reducing the working set even further. For server applications, NGen.exe makes no sense because only the first client request experiences a performance hit; future requests run at high speed.