CLR via C# 读书笔记1-4

23 篇文章 0 订阅

运行程序集代码

托管程序集由 metadata 和 IL 组成,IL 是一种不依赖 CPU 的机器语言,相对大多数的 CPU 机器语言来说,它属于高级语言。它可以建立/初始化对象,调用虚拟方法,直接操作数组,甚至进行错误捕获等,你可以把它看作一种面向对象的机器语言。
IL 与其他机器语言一样可以被写成程序集语言 (assembly language),Microsoft 提供了两个工具 ILAsm.exe,ILDasm.exe。
需要记住的是所有高级语言只提供了 CLR 机能的一个子集,而 IL 程序集语言能提供所有 CLR 的机能。所以如果你选择的语言缺失部分 CLR 的机能(而这部分机能又是你需要的)情况下,你可以使用IL 程序集语言或者其他支持该 CLR 的机能语言。
运行一个方法的时候, IL 必须先转换成本地 CPU 指令,这是由 CLR 的 JIT (just-in-time) 编译器完成的。图1-4 显示了首次调用一个方法的情形。
在 Main 方法调用前, CLR 检测出所有被主代码参照的类型,并为这些类型分配一块内部数据结构,以此管理对这些类型的调用。在图 1-4 中 Main 方法参照了一个类型: Console,相应的 CLR 分配了一个内部数据结构。 这个内部数据结构包含了Console 类型所有方法的入口列表,每个入口条目都记录了相应方法的实现地址。当初始化这个结构时, CLR 设定每个入口条目到一个 CLR 自身内部的未归档的方法(这个方法可称之为 JITCompiler)。当 Main 函数首次调用 WriteLine,JITCompiler 介入负责把这个方法的 IL 代码转换为本地 CPU 指令。

一经调用,JITCompiler 就把这些信息记录在案(前文提到的由CLR 建立的内部数据结构中),再次调用时就可以直接返回编译过的实现代码地址。
如图 1-5 显示了再次调用 WriteLine 的情形:

这就是首次调用较慢,后续调用快的由来(后续调用不需要JITCompiler再次解析)。由于 JIT 编译器把生成的本地 CPU 指令存在内存中,所以程序终止后这些信息会被销毁。所以当一个程序在关闭后再次执行,JIT 编译器又需要再次解析每个被调用的方法。
另外你需要知道 CLR 的 JIT 编译器可以像非托管的C++编译器一样优化本地代码,优化后的代码具有更好的运行效率,当然优化本身会花费一些时间。
C# 编译器有两个编译开关: /optimize 和 /debug,下表列出了这些开关对生成 IL 代码的影响,以及对JIT compiler 生成本地代码的质量。

编译开关设定C# IL 代码质量JIT 本地代码质量
/optimize- /debug-
(默认值)
未优化优化
/optimize- /debug(+/full/pdbonly)未优化未优化
/optimize+ /debug(-/+/full/pdbonly)优化优化


使用 /optimize-,未优化的 IL 包含了许多空(NOP) 以及跳转到下一行代码的分支,这些用来帮助 Visual Studio 在调试时支持 edit-and-continue 机能;而如果生成的是优化后的 IL 代码,将无法实现单步调试等机能,当然这时的 IL 代码更小更容易被人读懂。

另外仅当你 使用了/debug(+/full/pdbonly) 开关的时候,Program Database (PDB) 文件才会被生成。这个文件被用来帮助调试器找到本地变量并在 IL 指令与原代码建立映射。/debug:full 会让 JIT 编译器追踪每条由IL指令转换而来的本地代码,它让你可以使用 just-in-time 调试器来连接到一个已经运行的进程上。如果没有指定 /debug:full 开关不会去追踪并因此效率会高一些,占用的内存也少一些。当你使用Visual Studio调试器启动一个进程的时候,它将强制 JIT 编译器启动追踪 (无视 /debug 开关设定),除非你在Visual Studio中关闭Suppress JIT Optimization On Module Load (Managed Only) 选项。
托管程序的源代码需要编译,执行时又需要的解析,这导致许多有 C/C++ 背景的人怀疑其效率。实际上运行效率确实会受到影响,但这影响远比想象的要小,而且有时托管代码甚至能跑赢非托管程序。这有多方面的原因,比如:JIT 编译器实时解析 IL 代码,它能够获知更多运行时的系统情况。以下是托管代码胜过非托管程序的场景:
■ JIT 编译器可以判断应用程序是否运行在 Intel Pentium 4 CPU 上并利用 Pentium 4 集成的特殊指令。而非托管程序以最低/最通用的 CPU 为目标环境来编译,无法应用到这些特殊指令。

■ JIT 编译器能检测出哪些判断总是假的,比如以下判断:
if (numberOfCPUs > 1) {
...
}
这段代码在一台仅有1个CPU的机器上运行,经过 JIT 编译器处理后不生成任何 CPU 指令。
■ 程序运行时,CLR 能对代码的执行进行性能分析,并重新编译 IL 为本地代码。重新编译将根据已知的执行效率减少不正确的分支。当前版本的 CLR 尚未支持此功能,但今后可能会。
如果你遇到执行效率低下的情况,你可以使用.NET Framework SDK 中的 NGen.exe 工具。这个工具把一个程序集的所有 IL 代码编译为本地代码并存到一个文件中。运行程序,当程序集载入时 CLR 自动检查该程序集是否存在已编译的版本,如果存在 CLR 载入这些已编译代码(即:略过了实时编译)。需要注意的是 NGen.exe 必须对实际运行环境采用较保守的策略,换句话说它无法像JIT 编译器那样生成高优化率的代码。
另外你可以采用 System.Runtime.ProfileOptimization 类,在程序运行的时候来记录哪些方法被 JIT 编译了。下次再次启动你的程序时 JIT 编译器会在另外一个线程上同时编译这些方法(前提是你跑在多核CPU的机器上),从而使你的程序跑得更快。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值