在了解 CLR 运行之前让我们先简单了解一下 IL
除了编译器编译的 IL 代码,IL 也是一种汇编语言,也就是说我们可以直接编写 IL 代码,当然也有对应的 IL 编译器,值得一提的是对于面向 CLR 的其他语言,CLR 只开放了一部分功能,而 IL 可以访问 CLR 的全部功能。
前面一章我们介绍了 CLR 的所有初始工作,最后在调用 Main 入口方法的时候,CLR 需要将程序集中的 IL 代码转为 CPU 指令,也就是 CLR 中 JIT(just-in-time)编译器的职责,CLR 会即时编译 IL 代码
即时编译:在运行的时候才会进行编译(类似懒加载)当 CLR 运行并调用方法时做了如下几件事情 1、检测出所有方法中所有被引用的类型,并创建一个内部数据结构进行管理,每个类型的方法都会记录指向名为 JITComplier 函数的地址,2、在方法被调用的时候,函数会在与元数据中查找被调用的方法对应的 IL 代码,对其验证并将代码编译成 CPU 指令 3、将 CPU 指令存贮到动态分配的内存中 4、回到内部数据结构中,修改对应方法记录的地址,指向刚才编译好的 CPU 指令的地址 5、最后函数会回到内存当中去运行 CPU 指令
至此一个方法调用的全部流程就走完了,如果不终止程序(终止会将编译好的 cpu 指令丢弃),那么 CLR 在第二次调用方法时,直接在数据结构中找到对应的内存运行 CPU 指令,省去了上面的 2、3、4 步骤
CLR 的 JIT 编译器以及 C#编译器对本机代码的优化 C# 编译器 :/optimize 关闭 –> 编译出的 IL 代码会包含许多 NOP 指令 (no-operation 空操作) 和跳转执行,vs 就是利用的这些指令提供了调试的功能 /optimize 开启 –> 优化后的代码会更小,程序集也会相应变小,更方便阅读 IL 代码(一般估计不会有人去直接阅读 IL 查找问题吧)
JIT 编译器:在 /optimize 关闭 的情况下:/debug – 关闭(默认) –> 有优化 /debug (+/full/pdbonly) –> 未优化:编译器会生成 PDB 文件帮助编译器查找到局部变量并将 IL 代码映射到源代码方便调试,如果指定的是 /debug : full 开关,编译器还会记录每一条 IL 指令生成的本机指令,但会使用额外的时间和内存在 /optimize 开启的情况下:/debug (-/+/full/pdbonly) –> 有优化
虽然编译器在优化代码的过程中会占用额外的时间和内存,但是在实际运行阶段所带来的收益远远大于这些牺牲,并且性能上远远大于非托管代码,例如:1、JIT 编译器针对不同的 CPU 优化本机代码 2、会根据机器对特定的判断进行代码优化 3、CLR 会根据运行状态对代码评估并重新编译(还未实现)
最后再来简单了解一下 NGen.exe 工具 NGen.exe 是.net framework 提供的工具,它可以将代码提前编译好,这样 JIT 编译器不需要在运行是编译提升性能,但其实这个工具并不是很实用 1、因为 NGen 无法对代码进行最优的优化 –> 因为无法确定 CPU 2、对服务器提升不明显 –> 因为只是在第一次运行时有帮助,后面运行的时间时相等的 3、可能失去同步 –> 如果当前代码与执行环境不符合,那么就会从新用 JIT 编译
至此关于 CLR 如何与程序集工作就完成了,下一节我们将介绍.net Framework 的 Framework 类库以及 CTS CLS