.Net Framework Native Image and Troubleshooting

As an end user, you probably don't know what a native image is, because it's transparent to the user and you don't need to worry about it. As a .Net developer, it's important to understand the native image and how to diagnose it.

A native image is a compiled process-specific machine code that's installed into native image assembly cache. Usually it's at C:\Windows\assembly\NativeImagesXXX. This path is always in CLR probing path. When CLR finds a matching and valid native image for a managed assembly, the native image will be loaded. Otherwise, the managed assembly is loaded and jit'ed. There are two major advantages of using native images. The first one is to reduce application start up time. Since the native image is already compiled, there is no need for JIT compiler to compile the managed image at start up time. Second, it reduces memory footprint. The native image can be shared by multiple processes.

How to generate native images? ngen.exe. It provides  one action to generate native images:
install <assembly name>|<assembly path>
It compiles the assembly and its dependencies. If you pass option /queue, it won't generate the native image immediately. It's put into a queue. It's beneficial to put them to queue first when it takes a long time to compile if you have a large set of assemblies. When does it compile then? There will be a task in Windows to execute compiling at idle time. Native images don't always get generated if the dependencies are missing. You can use ngen.exe action
display /verbose
to view the details of the native image. There are hard dependencies and soft dependencies. If there are any missing hard dependencies, the native image won't be loaded even if it's generated.
How to specify the locations of the dependencies? You can set /ExeConfig to an executable and the executable's app.config will be used to find the dependencies. You have to make sure the dependencies set in this config are the same ones used at runtime. Otherwise, the native images won't be loaded.

You can generate multiple native images for the same assembly for different scenarios. This can be specified in ngen.exe action install by these options: /Debug, /Profile and /NoDependencies. Note that you can specified multiple scenarios in the same action. Which native image to load then? It depends on the scenario you set when you load the assembly. The corresponding native image for that scenario is loaded. Note that you don't have to specify the scenario in install action if it's not for any of those three scenarios.

Native images can be invalid if one of these occurs. The .Net framework is updated. The dependencies are updated or the assembly itself is updated (even the timestamp is changed). In this case, you can use ngen update to re-compile the native images.

While you believe a native image is generated and it should be loaded, but it's not loaded, what can you do? Fortunately, there's a tool in Visual Studio: fuslogvw.exe. I suggest you to enable "log all binds to disk" and use custom log path. There is a bug in setting custom log path. It may complain the path is not an absolute path. Just disable custom log path and try again. Usually this will solve the problem. fuslogvw.exe will then track all the assembly load as well as native image load. It generates the log files per assembly in the specified path. You can then open the log file for the failed native image.

The log shows the assembly to load, where it tries to find the dependencies, whether the assembly or the native image is loaded successfully and why it fails. And the reason why it fails is shown as a warning in the log. It contains these information for each attempt of loading.

You need some efforts to understand what the log tries to tell you, especially why the native image fails to load. The message is simple but it's not obviously related to the cause. Here are some common warning message and how to continue the investigation.

  1. Dependency assembly was not found at ngen time, but is found at binding time. Disallow using this native image.
    This is confusing, especially when you're quite sure the dependency assembly was deployed the same time with your assembly before ngen.  Actually, this means the dependency assembly is different from the one that's used at ngen time. It's different in version, public key token, culture or create timestamp. If the dependency assembly is deployed in multiple locations, the one used in ngen should also be the same used in runtime. You can check that from the assembly path from display /verbose and fuslogvw.exe's log.
  2. Rejecting native image because it failed the security check. The assembly's permissions must have changed since the time it was ngenned, or it is running with a different security context.
    The security settings when the native image is loaded  are different from when it's generated.
  3. Native image compile options do not match request.
    Remember the scenarios you use to generate native images? Yes. That is what it means. You compile it for one scenario but load it for a different one. For example, you compile it with /Profiler, but you don't specify any when you load it.
  4. Timestamps of the IL assembly does not match record in .aux file.
    ngen stores the create timestamp of the managed assembly in the .aux file. It compares the timestamp to make sure that the managed assembly which the native image is compiled from is actually the one that should be loaded. Otherwise, the native image won't be loaded. This can happen if you update the managed assembly. Or you compile the assembly at one location but there is a copy at a different location. The other one is loaded but it has a different timestamp.

There are also a few other reasons why native images fail to load. In short, that's because the native images are invalid.  These could happen when the system is updated, the assembly is updated, the dependencies are updated, or simply the timestamps are different.

That's it about native images. It's good to use native images to improve performance. The drawback is that it can become invalid due to a few factors. It's difficult to control all of the factors. You should measure the performance versus the effort.


Leave a comment

Your email address will not be published. Required fields are marked *