代码改变世界

1.5 本地代码生成器:NGen.exe

2011-11-15 16:45  iRead  阅读(1278)  评论(0编辑  收藏  举报

  使用.NET Framework配套提供的NGen.exe工具,可以在一个应用程序安装到用户的计算机上时,将IL代码编译成本地代码。由于代码在安装时已经编译好,所以CLR的JIT编译器不需要在运行时编译IL代码,这有助于提升应用程序的性能。NGen.exe能在两种情况下发挥重要作用:

  • 加快应用程序的启动速度  运行NGen.exe能加快启动速度,因为代码已编译成本地代码,运行时不需要再花时间编译。
  • 减小应用程序的工作集1  如果一个程序集会同时加载到多个进程中,对该程序集运行NGen.exe可减小应用程序的工作集(working set)。NGen.exe会将IL编译成本地代码,并将这些代码保存到一个单独的文件中。这个文件可以通过“内存映射”的方式,同时映射到多个进程地址空间中,使代码得到了共享,避免每个进程都需要一份单独的代码拷贝。

  安装程序为应用程序或程序集调用NGen.exe时,应用程序的所有程序集(或者那个指定的程序集)的IL代码会编译成本地代码。NGen.exe新建一个程序集文件,其中只包含这种本地代码,不含任何IL。新文件会放到C:\Windows\Assembly\NativeImages_v4.0.#####_64这样的一个目录下的一个文件夹中。在目录名称中,除了会包含CLR版本号,还会描述本地代码是为x86(Windows的32位版本)编译的,是为x64编译的,还是为Itanium编译的(后两者是Windows的64位版本)。

  现在,每当CLR加载一个程序集文件,都会检查是否存在一个对应的、由NGen生成的本地文件。如果找不到本地文件,CLR就像往常那样对IL代码进行JIT编译。然而,如果存在一个对应的本地文件,CLR就会直接使用本地文件中编译好的代码,文件中的方法不需要在运行时进行编译。

  从表面看,这似乎是一个完美的解决方案!一方面,我们获得了托管代码的所有好处(垃圾收集、验证、类型安全等等);另一方面,没有托管代码(JIT编译)的所有性能问题。但是,不要被表面现象迷惑。NGen生成的文件存在以下问题:

  • 没有知识产权保护  许多人认为,发布NGen生成的文件,而不发布包含原始IL代码的文件,可以保护知识产权。但遗憾的是,这是不可能的。在运行时,CLR要求访问程序集的元数据(目地是使用像反射和序列化这样的功能),这就要求同时发布包含IL和元数据的程序集。除此之外,如果CLR因为某些原因而不能使用NGen生成的文件(如后文所述),CLR会自动对程序集的IL代码进行JIT编译,所以IL代码必须处于可用状态。
  • NGen生成的文件可能失去同步  CLR加载由NGen生成的一个文件时,它将事先编译好的代码的大量特征与当前执行环境比较。如何一个特征不匹配,NGen生成的文件就不能使用。此时要换用正常的JIT编译器进程。下面列举了必须匹配的一部分特征: 
    • CLR版本:会随补丁或Service Pack发生改变
    • CPU类型:升级处理器后会发生改变
    • Windows操作系统版本:安装了新的Service Pack后,会发生改变
    • 程序集的标识模块版本ID(MVID):重新编译后会发生改变
    • 应用的程序集的版本ID:重新编译一个引用的程序集后,会发生改变
    • 安全性:吊销了之前授予的权限之后,安全性就会发生改变。这些权限包括声明性继承(declarative inheritance)、声明性链接时(declarative link-time)2、SkipVerification或者UnmanagedCode权限

注意,可以在更新(update)模式中运行NGen.exe,为以前用NGen生成的所有程序集再次运行NGen.exe。用户一旦安装了.NET Framework的一个新的Service Pack,这个Service Pack的安装程序就会自动在更新模式中运行NGen.exe,使NGen生成的文件与新安装的CLR版本保持同步。

  • 较差的执行时性能  编译代码时,NGen无法像JIT编译器那样对最终的执行环境做出许多假设。这会造成NGen.exe生成较差的代码。例如,NGen不能优化特定CPU指令的使用;静态字段只能间接访问,而不能直接访问,因为静态字段的实际地址只能在运行时确定。NGen到处插入代码来调用类构造器,因为它不知道代码的执行顺序,也不知道一个类构造器是否已经调用。(第8章“方法”会详细讲述类构造器的问题)。测试证明,相较于JIT编译的版本,NGen生成的某些应用程序在执行时反而要慢5%左右。所以,假如考虑使用NGen.exe来提升应用程序的性能,必须仔细比较NGen版本和非NGen版本,确定NGen版本不会变得更慢!对于某些应用程序,由于缩小工作集能提升性能,所以使用NGen仍然是有优势的。

  正是因为存在上述这些问题,所以在考虑使用NGen.exe时,务必非常谨慎。对于服务器端的应用程序,NGen.exe的作用并不明显,又是甚至毫无用处,这是因为只有第一个客户端请求才会感受到性能的下架,后续的所有客户端请求都能以全速运行。此外,对于大多数服务器应用程序,由于只需要代码的一个实例,所以无法从工作集的缩小中获得任何好处。还要注意,NGen 2.0之前生成的映像不能在不同的AppDomain之间共享。因此,如果一个程序要在跨AppDomain的环境中使用(比如ASP.NET),用NGen来生成它是没有任何好处的。(不过,这一限制在NGen 2.0之后已经不存在了。)

  对于客户端应用程序,也许能用NGen.exe加快启动速度,或者缩小工作集(如果程序集同时由多个应用程序使用)。即使一个程序集不是由多个应用程序使用,用NGen来生成它,也有助于增强工作集。此外,假如用NGen .exe来生成一个客户端应用程序的所有程序集,CLR根本不需要加载JIT编译器,从而进一步缩小工作集。当然,只要有一个程序集不是用NGen生成的,或者程序集的一个有NGen生成的文件无法使用,那么还是会加载JIT编译器,应用程序的工作集将随之增大。

 

  返回目录


  1、所谓工作集(working set),是指在进程的所有内存中,已映射的物理内存那一部分(即这些内存块全在物理内存中,并且CPU可以直接访问)。进程还有一部分虚拟内存,它们可能在转换列表中(CPU不能通过虚拟地址访问,需要Windows映射之后才能访问),还有另一部分存在磁盘的分页文件里。

  2、declarative inheritance权限是派生出程序集的那个类所要求的;declarative link-time权限是程序集调用的方法所要求的。