Speed

NGen Revs Up Your Performance with Powerful New Features

Reid Wilkes

This article is based on a prerelease version of the .NET Framework 2.0. All information herein is subject to change.

This article discusses:

  • How NGen can improve the performance of managed apps
  • How to know when NGen is right for you
  • New NGen features in the .NET Framework 2.0
This article uses the following technologies:
The .NET Framework and the CLR

Contents

Enter NGen 2.0
Is NGen Right for You?
NGen Hardbinding
Using the New NGen
Tying It All Together with an Example
Shutting Down

Most managed code developers have experimented with or at least heard of the NGen tool which ships with the Microsoft® .NET Framework. As a matter of fact, NGen has been around since the .NET Framework 1.0 release, and Microsoft has always used NGen on its own core Framework assemblies at .NET Framework installation time.

So why write an article on NGen now? There are two answers to this question. With the upcoming release of the .NET Framework 2.0, NGen has been greatly improved and offers a number of compelling new features to make it easier and faster to deploy high-performance managed applications. This article will introduce and describe some of these new features. In addition, this article will explain how and when you should consider using NGen to improve the performance of your apps.

Figure 1 NGen Execution

Figure 1** NGen Execution **

Let's start by quickly going over the basics of what NGen does. As you know, the just-in-time (JIT) compiler is used for compiling the Microsoft intermediate language (MSIL) code in a .NET assembly into native code for the local machine immediately before the code is executed. This is temporary code; it is created and stored in the running processes's memory space, which is reclaimed by the OS when the process dies. Thus, it will be regenerated each time a new process needs it.

Pre-compilation, or pre-JITing, is the creation of native code from MSIL on the client machine, but instead of being generated at run time it is done as a separate isolated step. The output from pre-JITing is persisted such that the native code can later be used in lieu of running the JIT at program execution time. NGen is the term used generically for pre-JIT technology in the common language runtime (CLR), and specifically for the command-line utility used to create and store the native code. The native code produced by NGen is stored in true Win32® PE files which are called "native images" or "NGen images" which are in turn stored in the "native image cache" or "NGen cache." "NGen cache" is also used more generally to refer to all the information that NGen stores—not only the actual native images but also the information about which assemblies have native images.

When creating native images, NGen simply loads the JIT compiler and invokes it to create native code for the assembly, which is then persisted into the native image and stored in the native image cache. Subsequently, when the assembly is used by a managed application, the CLR loader will locate, load, and use the native images. Figure 1 and Figure 2 illustrate all the steps involved in these mechanisms. (See the sidebar "Troubleshooting Native Images".)

The NGen technology in versions 1.0 and 1.1 of the .NET Framework has some serious drawbacks. All managed assemblies have dependencies, even if the only dependency is on mscorlib.dll. NGen images contain code and data structures which are based on assumptions about the dependencies of the assembly. They are therefore highly fragile to changes in those dependencies. If those dependencies change, it invalidates the code in the native image and the runtime will fail to use the native image, instead reverting to JIT compilation. This problem is particularly bad when considering that if a component shared by many applications is updated, for instance System.dll, then this will cause the native images for many applications on the machine to be invalidated.

Enter NGen 2.0

Fixing the fragility issues surrounding native images was one of the primary goals for the .NET Framework 2.0 NGen technology. There are a couple of approaches which were available from the start. The first is to actually remove the fragile interdependencies from native images. Most of these interdependencies come from compiler optimizations such as cross-assembly inlining of methods, so this option would have hurt performance enough to make it an unacceptable tradeoff. The other approach, and the one which has been taken, is to make the management of native images and their dependencies as automatic as possible in order to relieve developers from having to worry about whether their application performance will be affected if a shared component is updated.

Figure 2 How Native Images Are Used

Figure 2** How Native Images Are Used **

To achieve these goals, the NGen tool interface was changed to expose a new usage model. Instead of invoking NGen on an individual assembly, NGen is now invoked on an application. This is done by running NGen on the top-level assembly in the application (the top-level assembly is the assembly that is not depended on by any other assemblies). NGen will automatically discover all the available dependencies of the application and make sure that all the dependencies also have native images (this only applies to static dependencies that are recorded in the assemblies' manifest—any assembly explicitly loaded using methods like Assembly.Load will not be discovered by NGen). In addition, NGen keeps a record of all the dependencies of the application so that if a shared component is later updated, NGen can discover all applications that are affected by the update and regenerate native images where needed.

In addition to this tracking and updating mechanism, NGen now also supports deferred commands. This means that if you want to use NGen with a large application (which may take a significant amount of time to complete), you can queue this command to be completed by the new NGen service. The NGen service is a true Windows® service process that runs in the background and waits for idle time on the machine, at which point it will complete any outstanding compilation jobs that have been waiting in the queue.

The NGen queue supports a simple priority scheme to help establish the order in which applications are pre-compiled when submitting multiple jobs to the queue. The priorities also determine whether the service should wait for idle time on the machine to do the background compilation. This queuing functionality is especially useful in conjunction with the feature to update all native images that have been invalidated by the servicing of some shared component. Because this update may take a significant amount of time, you may not want to wait for the NGen update command to complete before exiting the component update installer. Instead, you may choose to queue the update command and allow the NGen service to take care of the compilation in the background.

In addition to the major new features, there have been a number of other changes that make NGen more usable and reduce the occasions when native images are thrown away in favor of running the JIT. For instance, native images created by NGen 2.0 can be shared across application domains in a process. In previous versions, native images could only be used by a single application domain. This meant that the performance gains achieved through NGen were largely lost for any application that used multiple application domains. One key place where this change will be realized is in ASP.NET applications, which previously were unable to use native images for this very reason.

Another improvement is that reflection is fully supported using only native images. This eliminates another entire class of scenarios in which native images could not be used. Also, the new generics feature is almost 100 percent supported for NGEN images. There are a few rather obscure cases in which the JIT might be pulled in, but these are rare. This particular feature was quite a feat and if you're interested in learning more about it, the implementation is well described at Combining Generics, Precompilation and Sharing Between SoftwareBased Processes. In addition, there have been quite a few small but effective performance tricks implemented in the .NET Framework 2.0 version of the CLR that focus specifically on the NGen case. Using NGen images is the primary way to achieve maximum performance with apps targeting the .NET Framework 2.0.

Is NGen Right for You?

So, NGen has some great features—but how do you know when to use it? Well, there are a couple of things NGen does for applications. It can reduce the private memory pages used by applications, which can, in turn, have a positive impact on system-wide memory pressure. It can also improve the startup time of the application. I'll look at each of these in turn.

When managed applications execute in the standard JIT case, they load an assembly containing MSIL code into memory and then invoke the JIT compiler on that MSIL code. The JIT compiler allocates private memory (which cannot be shared with other processes) and then writes the compiled machine code into that memory. There are several ways that native images improve the memory usage in the JIT case. First, the JIT compiler itself is not loaded into memory. The JIT compiler is implemented in a separate DLL from the rest of the runtime and is only loaded by the runtime if needed. This reduces memory consumption by a constant amount for any app using NGen.

Second, native images are loaded by the CLR just like any other DLL. This means that the actual memory pages containing the machine code can be shared by the OS between multiple processes. The effect of this is that any app using one of these shared native images is able to use these code pages already loaded into memory and thus reduce the number of new pages committed for that process. Correspondingly, the total amount of memory needed system-wide for all instances of the app using those native images is reduced. This benefit is only visible when considering shared components, such as the .NET Framework assemblies or apps that are used in multiple sessions on Terminal Server (see Figure 3).

Figure 3 Shared Memory

Figure 3** Shared Memory **

Application startup time can also be decreased by NGen under certain conditions. During startup, modules are loaded, memory is allocated, and the CLR executes a tremendous amount of code to initialize itself and, in some cases, JIT-compile the user code. Measurements in the CLR performance labs have shown significant startup time improvements when using native images instead of JIT compilation. These gains don't necessarily apply to all applications, though, and are usually limited to the warm startup case. Warm startup occurs when a managed program has been run previously and hence much of the code for the runtime and the native images has already been loaded from disk into memory. Cold startup—launching a managed application for the first time immediately after starting the computer—does not benefit as much from NGen because costly disk accesses dominate the startup time.

Despite these NGen benefits, it is not always the best choice. The performance issues can become very complicated, involving code and data access patterns, how code and data are laid out in a file, how many calls are made across module boundaries, and how much of the necessary infrastructure has already been loaded by other applications and is resident in memory. These and many other considerations make analysis difficult, but what matters are the real numbers derived from actually measuring your application. The CLR performance architects stress that doing careful, repeated measurements of your app in its various possible configurations is the only way to know the best course to take.

One more detail is also worth mentioning. If you are using strong-named assemblies, NGen is probably not a gain for you unless they are in the Global Assembly Cache (GAC). The CLR loader does extra validation on strong-named assemblies that aren't in the GAC, which causes nearly every page in the native image to get touched, thereby usually completely eliminating any startup gains that would be realized through the use of NGen.

There are a number of far more detailed discussions of NGen performance implications available online and easily located with a simple search. The authoritative reference is in Chapter 5 of "Improving .NET Application Performance and Scalability".

NGen Hardbinding

Another new feature of .NET Framework 2.0 NGen is hardbinding. In some cases, the JIT has a natural advantage over NGen because the JIT has a more complete knowledge of the virtual addresses of code and data across all modules and thus can create code that makes calls and loads data using actual virtual addresses as the targets. NGen, on the other hand, will traditionally build stubs into the code when writing calls or data accesses whose targets are in another assembly. These stubs are basically calls into a very small, tight routine in the runtime which calculates the actual virtual address of the desired method or data structure and then "backpatches" the call site by overwriting the call to the stub with a call to the actual target virtual address. This backpatching will cause a previously read-only code page to become writeable, and thus no longer shareable. Hardbinding allows NGen to create the same kind of code as the JIT and eliminate this backpatching (and subsequent reduction in shareable pages) at the cost of forcing native images to be very tightly bound. This tight binding means that all the native images that are hardbound to each other must be loaded at the same time. In terms of startup performance, this may turn out to be a detrimental effect if under normal circumstances not all of your assemblies are loaded at startup time.

The decision to use hardbinding is ultimately left to the runtime and NGen, but you can supply hints using the System.Runtime.CompilerServices.DependencyAttribute and also the System.Runtime.CompilerServices.DefaultDependencyAttribute. The former attribute is placed on an assembly and tells NGen how likely it is for a specific dependency to be loaded. The latter attribute is placed on a dependency and states how frequently that dependency will be loaded. If no DependencyAttribute is specified on a parent, then the DefaultDependencyAttribute of the child is used. Both attributes get their values from the System.Runtime.CompilerServices.LoadHint enumeration, which has values of Always, Default, and Sometimes. Using the value of Always is a hint to the runtime and NGen to use hardbinding for that dependency. The other values mean the same—don't use hardbinding.

If you are considering using these attributes, you need to carefully weigh the data from doing performance experiments to make sure it is going to give you what you need. Whether this is the right choice depends heavily on the particular application in question. Microsoft uses hardbinding attributes on only a few of the base .NET Framework assemblies like mscorlib.dll. These assemblies contain a DefaultDependencyAttribute set to LoadHint.Always, which means that unless overridden, other assemblies will hardbind to them by default. Also, be aware that it's important to get base addresses right with hardbinding since hardbound assemblies bear a higher cost for base address relocations. For more information, see the sidebar, "NGen Image Base Addresses."

Using the New NGen

Now let's look at how to actually use these new features. Note that as of this writing, the information here is more current than the information in the .NET Framework documentation for Beta 2.

You can only access NGen functionality through the NGen.exe command-line tool. The syntax for using this tool has changed dramatically with the .NET Framework 2.0. Although the old syntax is still supported, it does not make full use of the new functionality. I recommend moving completely to the new syntax, so I won't discuss how the old commands work.NGen Image Base Addresses

Getting the base address right for NGen images is at least as important as it is for unmanaged code modules. If an NGen image misses its preferred base address, the CLR has to execute special code to patch up the native image at its new address. This effect is even more pronounced for hardbound native images since they require more work to patch up and are more sensitive to relocations. If any hardbound dependency gets rebased then all the callers into that assembly must also be fixed up at load time. The way to set the base address for a native image is by setting the base address on the assembly created by the language-specific compiler. The C#, Visual Basic®, and C++ compilers all have options that allow for controlling the base address of the assembly produced. In addition, the ".imagebase" directive in MSIL will cause ILASM to produce an assembly with the specified base address. NGen will retain this base address for the native image except when the assembly is an executable (.exe). In this case, the base address on the native image will be offset enough to allow room for the executable itself to load at its preferred base address. Because the native images are true Win32 PE files, it is possible to view the preferred base address of a native image in the cache using a tool such as dumpbin.exe.

Every NGen command line now begins with an action specified as the first parameter. The actions available are shown in Figure 4. The install action adds the native image for a top-level assembly, known as a root, to the NGen cache. NGen will also automatically find all the dependencies of the root and add native images for those assemblies to the cache as well. The command line needed to install the root AccountingApp.exe into the NGen cache would look like the following:

ngen install AccountingApp.exe

Figure 4 NGen.exe Command-Line Interface

NGEN INSTALL <root name> [<build configuration flags>] [<config flags>] [<option flags>] [/queue:<priority>] NGEN UNINSTALL (<root name> | * ) [<build configuration flags>] [<config flags>] [<option flags>] NGEN DISPLAY [<root name>] [<build configuration flags>] [<config flags>] [<option flags>] [/verbose] NGEN UPDATE [ * ] [<option flags>] [/queue] NGEN EXECUTEQUEUEDITEMS <priority> [<option flags>] NGEN QUEUE ( pause | continue ) [<option flags>] Scenario Flags: /Debug /Profile /NoDependencies Config Flags: /AppBase:<path> /ExeConfig:<full path to exectable> Option Flags: /nologo /silent /verbose Priority Values: 1, 2, or 3. Root Names: Either a file name or assembly display name for assembly in GAC.

When locating the dependencies of AccountingApp.exe, NGen will use the same assembly-probing logic that the CLR uses at run time. NGen defaults to using the directory containing AccountingApp.exe as the AppBase. This means that assembly probes will be performed starting in this directory. This is different from previous versions of NGen, where the AppBase was set to the current directory. For the most part, this new behavior works as expected and does the right thing.

There are, however, times when NGen needs some special help to locate dependencies. The /appbase switch allows you to override the AppBase from the directory containing the assembly to any directory you specify. The /execonfig switch can be used when installing a DLL assembly to the native image cache. This switch allows you to specify the name of an executable that has a configuration file associated with it. NGen will use the information in that executable's configuration file to guide the lookup of dependencies for the DLL being installed.

Assemblies in the GAC can be installed to the NGen cache as well. The syntax required to do this involves using the display name of the assembly instead of the file name:

ngen install "payroll, Version=1.0.1.2, Culture=neutral, PublicKeyToken=ab7e26f746a3c28e, processorArchitecture=MSIL"

NGen also supports various build configuration flags, which can be supplied with the install action. The most useful of these are debug and profiling. The debug configuration flag tells NGen to create a set of native images for the root which can be used in a debugger. If you try to launch an application under a debugger using default native images, the runtime will instead use the JIT to create debuggable code. Passing the debug flag to NGen causes the debuggable code to be created in the native image. This allows you to launch a program in the debugger and still have that program use code from the native images. This is the only way to attach to a running program using native images with a managed debugger and be able to see call stacks, locals, and all the other familiar debugging tricks. Similarly, the profiling switch tells NGen to create native images, which can be used under a profiler. By using these switches, it is possible to create multiple build configurations for a single root in the NGen cache. For instance, I could run the following two commands:

ngen install AccountingApp.exe ngen install AccountingApp.exe /debug

After issuing these two commands I would then have both a default and debug set of native images for AccountingApp in the native image cache.

The uninstall command is used much like the install command but, quite obviously, removes roots and their dependencies from the NGen cache. Because NGen tracks the dependencies of roots in the cache, you don't have to worry about accidentally removing the native image for a shared component that some other root has a dependency on. NGen will make sure that as long as some installed root has a dependency on a shared component, the native image for that shared component will remain in the cache. Uninstall, like install, supports the build configuration (debug, profile) and assembly probing (appbase, execonfig) flags. This allows you to selectively remove the native images for a debug configuration of a root without removing the images for the default configuration of that same root. You should note, however, that running uninstall without any flags tells NGen to uninstall all the configurations for the specified root. Here are some examples of uninstall commands:

ngen uninstall Payroll.dll ngen uninstall "payroll, Version=1.0.1.2, Culture=neutral, PublicKeyToken=ab7e26f746a3c28e, processorArchitecture=MSIL" ngen uninstall "payroll, Version=1.0.1.2, Culture=neutral, PublicKeyToken=ab7e26f746a3c28e, processorArchitecture=MSIL" /debug ngen uninstall payroll

The first command removes the root Payroll.dll, which is located in the current directory. This would include any build configurations for that root. The second removes the root for the assembly in the GAC that matches the given display name. This command would also remove all build configurations for the root. The third command only removes the debug configuration for the root "payroll, Version=1.0.1.2, Culture=neutral, PublicKeyToken=ab7e26f746a3c28e, processorArchitecture=MSIL". The final command is the simplest form of uninstall and will remove all roots with the simple name payroll, which would include all the roots in the previous examples.

The display command is how you can peer into the native image cache. It can be used with no arguments, like so:

ngen display

This would display information about all the roots in the cache. It can also be used with a whole or partial assembly name:

ngen display payroll.dll ngen display payroll

Both of these commands would display only the information for the root Payroll.dll. The first command only works if you either specify the full path to Payroll.dll, or are in the same directory as Payroll.dll. The second command would also display the information for the root named "payroll, Version=1.0.1.2, Culture=neutral, PublicKeyToken=ab7e26f746a3c28e, processorArchitecture=MSIL". Display outputs two sections of output: the first is a list of all root assemblies, and the second is a flat list of every single native image in the system's cache. The /verbose switch will force much more state information to be printed, but I won't go into those details here. NGen display will also print a list of roots which depend on a given assembly's native image if you use syntax like the following:

ngen display payroll

This command will display all the roots with a simple assembly name of payroll, all roots that depend on an assembly with a simple assembly name of payroll, and all the native images for assemblies with a simple assembly name of payroll. This can be useful for determining how many roots will need to be regenerated after an update to a shared component.

The next NGen action is "update." The update action takes no arguments (that have been covered yet here) and simply forces NGen to recalculate the dependencies of all roots in the cache, and generate new native images for anything that is not up to date. Update is primarily intended to be used after a shared component on the system has been updated. The command simply looks like:

ngen update

If there are a large number of roots in the cache, then the NGen update command may take a significant amount of time to complete. It is primarily for this reason that the deferred NGen capabilities were added for the .NET Framework 2.0.

Deferred NGEN allows you to issue a command to NGen, through the command-line tool, which tells NGen to do some action using the service in the background. This is accessed through the /queue switch, which is supported for both the install and update actions. For instance, issuing

ngen update /queue

tells NGen to do a full update action, but to wait to do all the work until the machine is idle. The command itself would return immediately, and thus a script that called it would be able to finish even though the NGen work had not yet completed. The deferred command will be completed by the NGen service process following the idle time detection rules. These are subject to fine-tuning before the final product releases so I want to avoid providing too much detail. In general, though, the service notices that there has been no user input on the machine for a timeout period, then the service declares the machine idle and will begin launching normal priority worker processes to perform any necessary compilation.

The install command has an additional feature: the /queue switch accepts a numeric argument between 1 and 3 that represents the priority with which to perform the deferred install command. Priority 1 and 2 commands are completed by the service in the background, but without waiting for idle time. Instead, workers are immediately launched by the service to begin doing the compilation work. Priority 3 commands follow the same idle time rules as update commands do. As a matter of fact, update commands are always implicitly set at Priority 3. An install command might look like this:

ngen install AccountingApp.exe /queue:2

This command would add a new root to the cache for AccountingApp.exe. It would then tell the service to immediately begin compiling native images for AccountingApp.exe and all of its various dependencies.

The difference between priority 1 and priority 2 is that the service ensures that commands issued with a priority of 1 are completed before commands issued with a priority of 2 and so on. Other than that, priorities 1 and 2 behave the same way in that neither wait for idle time on the machine, but rather immediately cause the service to begin the background compilation. The priorities can also be useful in certain scenarios when used in conjunction with the executequeueditems action. This action forces commands in the queue to be immediately executed. It is a synchronous command, so it will not return until all the work specified is completed. Executequeueditems takes a priority number as an optional argument, and will only process queued commands whose priority numbers are less than or equal to that particular level. For instance,

ngen executequeueditems 2

will force NGen to synchronously process all commands in the queue with priorities 1 and 2. The obvious conclusion, then, is that

ngen executequeueditems 3

is the way to force every queued command to be completed synchronously. The default priority for executequeueditems is 3.

One thing to note is that synchronous commands are carried out without going through the service at all. This means that NGen commands without the /queue switch, including executequeueditems, are completed entirely by ngen.exe and the worker processes it launches. These commands will work even if the service is disabled, and executing these commands will not cause the service to be started. The service itself is a Windows service process whose normal state is to be completely shut down. When commands are queued to the service through ngen.exe, the service is started and remains started even through reboots until there are no further queued commands to process. At this point, the service sets itself to not start up at reboot time and shuts itself down. The service will then remain shut down until yet another queued NGen command is issued.

The last action I need to cover makes for a nice segue into a discussion of some common usage scenarios. The queue action (not to be confused with the /queue switch) is the way to control the activity of the service. There are three supported arguments to the queue action: pause, continue, and status. It is recommended that any installer issue a queue pause command before even beginning to copy files to the machine:

ngen queue pause

This will ensure that the service does nothing in the background while the installer is copying files, putting assemblies into the GAC, or pre-compiling them. The purpose is to avoid conflicts between the installer and the service trying to access the same resources. At its completion, the installer can issue the command,

ngen queue continue

which will allow the service to resume processing any queued commands, or go back into a shutdown state if there is no more work to do. The status argument to ngen queue causes diagnostic information about the service process to be printed out to the console.

Tying It All Together with an Example

Let's put all this together by examining a scenario and looking at what happens behind the scenes by using the display command. In this particular scenario, we are building a deployment package for a suite of tools used by small businesses. The core tools are Inventory.exe, TaxWizards.exe, and Books.exe. In addition to these core tools, the package deploys a DLL which is shared by all applications in the suite, Payroll.dll, and a component from a third-party development toolkit called Chart.dll which could potentially be used by other applications on the user's system. The installation requirements allow a relatively short time for installation which prevents running NGen on every component at installation time. However, these applications are relatively large and the startup time gains from using NGen have been shown to be significant. The decision has been made to finish the pre-JITing of the flagship application, Books.exe, during installation and then queue the commands to pre-compile the rest of the applications after installation. Although somewhat contrived, this example scenario gives us a broad coverage of all the core types of tasks supported.

The install script for this package might look this:

ngen queue pause ... (xcopy all files to application directory) ... (install Payroll.dll to GAC) ... (install Charts.dll to GAC) ... (add registry keys for application) ngen install Books.exe /queue:1 ngen install Inventory.exe /queue:2 ngen install TaxWizards.exe /queue:2 ngen update /queue ngen executequeueditems 1 ngen queue continue

First, the script pauses the service to prevent any resource conflicts, then copies all files, puts the shared components into the GAC, and sets up necessary registry keys. The script then installs the three top-level assemblies as roots in the native image cache by using the queued NGen install commands. After this, the script issues a queued NGen update. The purpose of this is to ensure that any native images that depend on the shared component Chart.dll are updated if necessary. This would only be necessary if an assembly that depends on Chart.dll is automatically upgraded to the new Chart.dll currently being installed. That could be the case if the update to Chart.dll differs only in the build revision part of the version number (for example, 1.0.0.1 to 1.0.0.3) or if the binding policy affecting this component was updated. At any rate, the safe bet is to always run the NGen update command if installing a component to the GAC.

Next, in the script, the executequeueditems command is issued. The argument to this command is 1, which tells it to only force the processing of queued commands with priorities of 1. Finally, the NGen queue is reactivated with the "queue continue" command. After this script completes the last command, the service will automatically begin processing the priority 2 commands, which were queued. Remember that priority 2 commands do not require the service to wait for idle time, but rather are processed eagerly in the background. At some point in the future when the service detects that the machine is idle, it will complete the queued update command, which is implicitly at priority level 3.

Let's look at the state of the cache before the script is run:

>ngen display Microsoft (R) CLR Native Image Generator - Version 2.0.41203.00 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. Roots: C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86ret\mscorlib.dll Native Images: mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

Note that the display output is trimmed in these examples to include only the relevant information. In this output there is only one root added to the cache: mscorlib.dll. You can also see under the "Native Image:" heading that there is one native image—the native image for the mscorlib root. Now look at Figure 5, which shows the output from the display command after running through all three install commands in the script. You can see that there are four roots: the mscorlib root, which existed previously, and a new root from each of the ngen install commands. You'll also see that the new roots are annotated with the tag "StatusPending". This means that the installation of these new roots has only been queued and no actual work has yet been done. This is also evidenced by the fact that there is still only one native image listed—the native image for mscorlib.

Figure 5 Output of Display Command

>ngen display Microsoft (R) CLR Native Image Generator - Version 2.0.41203.00 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. Roots: C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86ret\mscorlib.dll D:\Scratch\SBApp\Books.exe (StatusPending) D:\Scratch\SBApp\Inventory.exe (StatusPending) D:\Scratch\SBApp\TaxWizards.exe (StatusPending) Native Images: mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

Figure 6 shows the display output after running the queued update command. The difference in this output versus Figure 5 is that now even the mscorlib.dll root has been marked as "StatusPending". This gives us a little insight into how the update command is implemented. Update begins by marking all the roots in such a way that tells the service it needs to look at the root again, regenerate a list of the dependencies, and generate native images for all assemblies in that list that don't have valid native images already in the cache. This explains the new status you see on the mscorlib.dll root. You can also see that there is still a native image for mscorlib.dll. Update does not get rid of native images unless they prove to be invalid. When this update is processed by the service it will detect that there is already a valid native image for mscorlib.dll in the cache and will not regenerate a new one.

Figure 6 More Display Command

>ngen display Microsoft (R) CLR Native Image Generator - Version 2.0.41203.00 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. Roots: C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86ret\mscorlib.dll (StatusPending) D:\Scratch\SBApp\Books.exe (StatusPending) D:\Scratch\SBApp\Inventory.exe (StatusPending) D:\Scratch\SBApp\TaxWizards.exe (StatusPending) Native Images: mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

After running the executequeueditems command you can take another snapshot look at the cache, shown in Figure 7. The root for Books.exe is now complete as it no longer has the "StatusPending" tag attached to it. The other roots that were added are still pending. There are now native images created for Books.exe and its dependencies: Chart.dll and Payroll.dll. The final command in the script tells the service to once again begin doing background compilation. As stated earlier, the service will process any queued commands in decreasing priority order. Therefore the queued priority 2 installs for Inventory.exe and TaxWizards.exe will be completed first (and without waiting for idle time). The queued update command will be executed last (at idle time) and by then, provided no other commands are executed, the only work left to be done for the update is to make sure there are good native images for the root mscorlib.dll. The final state of the cache after all work has been done is shown in Figure 8.Troubleshooting Native Images

The native images produced by NGen are stored in a private cache maintained by the CLR, and the CLR uses them automatically. Overall, the system is very much a black box and is Just Supposed to Work. It's useful, however, to be able to confirm that native images are actually being used by your application to make sure that you are not hitting an edge condition which causes the native images to be discarded. You can see whether native images are being used in your process by attaching a debugger and looking to see if the native image PE file is loaded into the process. You must attach with the debugger to do this test. Unless you created debug native images, the native images will not be loaded at all if you launch the program under the debugger. The name of the native image file is the same as the name of the original assembly, with ".ni." inserted between the file name and the file extension. For instance, mscorlib.dll's native image will be named "mscorlib.ni.dll". Another way to examine how native images are being used is through the fuslogvw.exe tool which ships with the .NET Framework SDK. This tool has a mode enabled by a radio button on the main panel to log binding information for native images. Fuslogvw also provides the useful feature of logging information about why native images are rejected when that is an issue.

Figure 8 Final Cache State

>ngen display Microsoft (R) CLR Native Image Generator - Version 2.0.41203.00 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. Roots: C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86ret\mscorlib.dll D:\Scratch\SBApp\Books.exe D:\Scratch\SBApp\Inventory.exe D:\Scratch\SBApp\TaxWizards.exe Native Images: Books, Version=1.0.1.1, Culture=neutral, PublicKeyToken=3a0f3302b4d4c63a mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Payroll, Version=1.0.1.1, Culture=neutral, PublicKeyToken=3a0f3302b4d4c63a Chart, Version=3.1.0.0, Culture=neutral, PublicKeyToken=3a0f3302b4d4c63a Inventory, Version=1.0.1.1, Culture=neutral, PublicKeyToken=3a0f3302b4d4c63a TaxWizards, Version=1.0.1.1, Culture=neutral, PublicKeyToken=3a0f3302b4d4c63a

Figure 7 The Cache

>ngen display Microsoft (R) CLR Native Image Generator - Version 2.0.41203.00 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. Roots: C:\WINDOWS\Microsoft.NET\Framework\v2.0.x86ret\mscorlib.dll (StatusPending) D:\Scratch\SBApp\Books.exe D:\Scratch\SBApp\Inventory.exe (StatusPending) D:\Scratch\SBApp\TaxWizards.exe (StatusPending) Native Images: Books, Version=1.0.1.1, Culture=neutral, PublicKeyToken=3a0f3302b4d4c63a mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Payroll, Version=1.0.1.1, Culture=neutral, PublicKeyToken=3a0f3302b4d4c63a Chart, Version=3.1.0.0, Culture=neutral, PublicKeyToken=3a0f3302b4d4c63a

Shutting Down

Once the service completes all the work queued during the installation script, it will shut itself down so that it is no longer consuming any resources. It will only start running again if another command is queued through NGen. It has been one of the primary design goals of this feature that it remain as invisible and unobtrusive as possible. As a whole, in fact, the goal with the NGen technology in the CLR is to provide a robust, hands-off infrastructure for gaining the performance benefits of pre-JITing. The .NET Framework 2.0 features of NGen go a long way towards meeting this goal.

Now that you've learned about the new features of NGen that are available in the .NET Framework 2.0 release, you're all ready to incorporate this technology into your own managed applications. Remember, measure your application carefully in order to decide whether NGen is right for both your application and your customer's scenarios based on the data that you collect. With all of the improvements in the upcoming .NET Framework 2.0, NGen may be just the way to give your managed applications the performance edge that you need.

Reid Wilkes works for Microsoft. He is a member of the quality assurance team for the common language runtime and focuses on the NGen technology.