将C#编译成中间语言,然后中间语言由CLR执行。CLI标准中定义了中间语言的规范。我们可以把第1章的“Hello World!”程序加载到.NET SDK提供的中间语言反汇编器(Intermediate Language Disassembler,ILDASM)中,一探中间语言的究竟。ILDASM以树型视图列出程序集中的数据类型,你可以展开一个方法,查看C#编译器生成的中间语言代码。如代码清单2-1所示,中间语言很像汇编语言,实际上它就是CLR中的汇编语言。称其为中间语言是因为它是介于特定语言和特定平台之间的一个中间步骤。
代码清单2-1 HelloWorld.exe主方法中间语言
CLR不是解释器,并非每次执行的时候它都重新翻译中间语言代码。虽然解释器提供了许多灵活的方案(比如Windows脚本宿主中的JScript解释器),但它通常不是一个高效的运行时平台。实际上CLR将中间语言代码在执行之前就编译成机器码——这就是即时编译。即时编译的过程需要一些时间,但是对于程序的每个部分,它通常意味着每个进程只会受到一次性能上的影响。当代码编译完成之后,CLR下次需要时仅仅执行编译好的版本,执行速度与传统的编译好的代码一样迅速(有时甚至更快)。
虽然JIT编译阶段增加了一些复杂性,并一定程度上影响了初次运行时的性能,但是JIT编译器与CLR的优点超过了JIT编译耗费的时间,因为:
l 托管程序消耗的内存更少。通常中间语言的程序代码体积比本地代码小。换句话说,托管程序的工作集(working set)——程序消耗的内存分页——一般比本地程序小。通过一定的转化,可以将本地程序的工作集减小到托管程序的水平,但有了CLR,就不需要为此做任何工作。
l 只有被执行的中间语言代码才会被JIT编译器编译。一般来说,中间语言代码比机器码更加紧凑,所以将编译过的代码控制在最小程度,有利于减小程序所占内存的空间。
l CLR能跟踪操作频繁的调用。当某个经过JIT编译的代码段长时间没有被调用,CLR会释放其所占的空间。这段代码直到再次调用时才会重新编译。
CLR还能在程序执行时进行优化。对于本地程序,只能在编译时确定优化选项。然而当编译发生在CLR运行时中,CLR则可在任何时候执行优化。一种可能的情形是,CLR以默认方式,只使用有限的优化选项快速地编译代码。当某段代码被频繁调用,CLR可以通过更多的优化选项将它重新编译,使其执行得更加快速。例如目标平台的CPU数量或CPU体系结构的不同,CLR的效率模型也会有很大的差异。对于本地程序,无论是在运行时或者编译阶段,都必须进行更多的人工干预以适应上述情形。但有了CLR,创建多处理器性能就更为轻松了。另外,如果CLR发现一些分散在程序不同地方的代码被调用得相当频繁,CLR可以将这些代码移到内存中。这样它们处于同一组内存分页中,进而减小了分页错误的数量,并提高了程序运行中缓存的命中率。
CLR是个灵活的平台,它的优点显著地超过初次执行时JIT编译对性能的影响。本节所介绍的只是一部分原因。