前面提到过,托管程序集同时包含元数据和IL,IL是一种和CPU无关的机器语言,IL要比大多数CPU机器语言高级很多,它可以访问和操作对象类型,提供相应的指令用于初始化对象,在对象上调用虚方法,并可以直接操作数组元素,他甚至提供了用于抛出和捕捉异常的指令。可以把IL想像成是一种面向对象的机器语言。
大部分.Net程序员使用C#,C++/CLI或者是VB编程,这些高级语言会被编译器翻译成对应的IL,当然IL也可以用汇编语言来写,微软为此提供了ILAsm和ILDasm工具用于汇编和反汇编IL。
但需要注意的是,高级语言通常只揭示CLR的一个功能子集,然而IL确允许开发人员访问CLR的所有功能。所以,如果选择的语言隐藏了需要的那部分CLR功能,就可以选择IL或者提供这部分功能的语言来编写这部分代码。
接下来说说JIT编译:
首先考虑下面这个简单的C#方法:
static void Main()
{
Console.WriteLine("Hello");
Console.WriteLine("Byebye");
}
这个方法中,Main方法调用了单件类Console,使得CLR分配了一个单独的内部结构,在这个结构中,Console的每一个方法都有一条记录,每条记录均容纳了一个地址,根据这个地址既可以找到方法的实现。当对这个结构进行初始化时,CLR将每条记录都设置成CLR内部包含的一个未文档化的方法,暂时就把这个方法叫做JITCompiler吧。
Main方法首次调用WriteLine的时候会调用JITCompiler方法,JITCompiler方法负责将一个方法的IL代码编译成本地CPU指令,由于IL是即时(In Time)编译的,所以通常将CLR这个组件称为JITer。
JITCompiler被调用的时候,它需要知道要调用哪个方法以及具体是什么类型定义了该方法,然后JITCompiler会在定义程序集的元数据中搜索被调用方法的IL。JITCompiler接下来要验证IL代码,并将IL代码编译成本地的CPU指令,本地CPU指令保存在一个动态分配的内存块中,然后JITCompiler回到CLR为类型创建的内部数据结构,找到与被调用的方法相对应的那一条记录,然后将最初调用它的那个引用替换成内存块(包含刚才编译好的CPU指令)的地址,最后,JITCompiler会跳转到内存块中的代码,这些代码正是WriteLine方法的具体实现,代码返回时,会返回到Main中的代码并继续执行。
然后Main第二次调用WriteLine,由于之前已对WriteLine的代码进行了验证和编译,所以会直接执行内存块中的代码,完全跳过JITCompiler方法。
所以说,同一个方法只有在首次调用的时候才会造成一定的性能损失,之后对该方法的调用都会以本地代码的形式全速运行,因为不需要再次执行验证和编译本地代码。
需要注意的是,CLR的JIT编译器会对本地代码进行优化,生成这些代码需要更长的时间,但会获得更出色的性能。例如C#的编译器中有两个开关:/optimize和/debug,在VS的Debug配置中,Debug配置是/optimize-和/debug-full,而Release的配置是/optimize+和/debug:pdonly。
N多微软神作的作者Jeffrey Richter认为IL最大的优势并不在于它是脱离了底层CPU的一种抽象,而是它所提供的可靠性和安全性。
2008年10月27日星期一
订阅:
博文评论 (Atom)

没有评论:
发表评论