以前只知道托管语言比非托管语言慢,具体为什么慢 、慢在哪里 、还有就是web部署以后为什么第一次打开总是很慢了解的一知半解,最近看了CLR via C#后总算了解了。
Main方法执行之前CLR会对检测出Main代码引用的所有类型,并分配一个内部数据结构来管理对引用类型的数据访问。如图Main方法引用一个Console类型,导致CLR分配一个内部结构。在这个内部结构当中,Console类型定义的每个方法都有一个对应的记录项。每个记录项含有一个地址,根据地址即可找到方法的实现。对这个结构初始化时,CLR将每个记录项都设置成(指向)包含在CLR内部的一个未编档函数。称之为JITCompiler。Main方法首次调用WriteLine时,JITCompiler函数都会被调用。JITCompiler函数负责将方法的IL编译成本机的CPU指令。由于IL是“即时”(just in time)编译的,所以通常将CLR的这个组件成为JITter或者JIT编译器。
好吧,以上是JIT编译器的定义,敲得好心累,不过敲一遍确实有助于理解。
JITCompiler会在定义程序集的元数据当中查找被调用方法的IL,然后验证IL代码,并将IL代码编译成本机CPU指令。本机CPu指令保存到动态分配的内存当中。然后JITCompiler回到CLR为类型创建的内部数据结构,找到与被调用方法对应的那条记录,修改最初对JITCompiler的引用,使其指向内存块的地址。
JITCompiler函数跳转到内存块中的代码,这些代码正是WriteLine方法的具体实现。
代码执行并返回继续执行。
第二次调用WriteLine,会直接执行内存中的代码块,完全跳过JITCompiler函数。
所以说方法仅在第一次调用时才会有性能损失,以后对该方法的调用都是以本机代码德兴市全速运行,无需重新验证IL并编译成本机代码。但是程序一旦终止,编译好的代码也会被丢弃。
非托管代码是针对一种具体CPU平台编译,一旦调用,代码就能直接执行。但在现在这种托管环境中,代码的编译分两个阶段进行。首先,编译器遍历源代码,做大量工作生成IL代码,但正正想要执行,这些IL代码必须在运行时编译成本机CPU指令,这需要分配更多的非共享内存,病要花费额外的CPU时间。好在微软进行了大量的性能优化工作,将这些额外的开销保持在最低限度之内。