托管应用程序的全局分配配置文件定义了垃圾回收器对与应用程序相关的内存进行治理的工作量有多大。GC 治理内存的工作量越大,GC 所经历的 CPU 周期数就越多,而 CPU 运行应用程序代码所花费的时间也就越短。分配配置文件由已分配对象数、对象的大小及其生命周期计算得出。缓解 GC 压力的一种最明显的方法就是减少分配的对象数量。使用面向对象设计技术将应用程序设计为具有可扩展性、模块化和可复用的特性,往往会导致分配的数量增多。抽象和“精确”都会导致性能下降。
典型的实际分配配置文件介于上面提到的两种分配配置文件之间。分配配置文件的一个重要度量标准是 CPU 花在 GC 上的时间占其总时间的百分比。您可以通过 .NET CLR Memory:% Time in GC 性能计数器获得这一数字。假如此计数器的平均值大于 30%,则您可能需要对您的分配配置文件进行一次仔细的检查。这并不一定意味着您的分配配置文件有问题,在某些占用大量内存的应用程序中,GC 达到这种水平是必然的,也是正常的。当您碰到性能问题时,首先应该检查此计数器,它将立即显示出您的分配配置文件是否出现了问题。
提示:假如 .NET CLR Memory:% Time in GC 性能计数器指示您的应用程序花在 GC 上的平均时间高于它的总时间的 30%,则表明您需要对您的分配配置文件进行一次仔细的检查。
提示:GC 友好的应用程序中包含的第 0 代对象远远多于第 2 代对象。此比率可以通过比较 NET CLR Memory:# Gen 0 Collections 和 NET CLR Memory:# Gen 2 Collections 性能计数器的结果来得出。 用于分析的API和CLR分析器
托管程序集是托管代码的分发单位,它由 Microsoft 中间语言(MSIL 或 IL)构成,适用于所有的处理器。CLR 的实时 (JIT) 功能可将 IL 编译成优化的本机 X86 指令。JIT 是一种执行优化操作的编译器,但是由于编译是在软件运行时进行的,并且仅当第一次调用方法时才会进行,因此进行优化的次数需要与执行编译所花费的时间保持平衡。通常,这对于服务器应用程序并不重要,因为启动时间和响应对于它们来说通常都不构成问题;但对于客户端应用程序来说,却十分重要。请注重,安装时可以通过使用 NGEN.exe 执行编译来加快启动时间。
CLR 可提供两组不同的类型:引用类型和值类型。引用类型总是分配到托管堆中,并按引用传递(正如它的名称所暗示的)。值类型分配到栈中或在堆中内联为对象的一部分,默认情况下按值传递,不过您也可以按引用来传递它们。分配值类型时,所需的开销非常小,假设它们总是又小又简单,当它们作为参数进行传递时开销也会很小。正确使用值类型的一个很好的示例就是包含 x 和 y 坐标的 Point 值类型。
反射是由 CLR 提供的一种机制,用于在运行时通过编程方式获得类型信息。反射在很大程度上取决于嵌入在托管程序集中的元数据。许多反射 API 都要求搜索并分析元数据,这些操作的开销都很大。
这些反射 API 可以分为三个性能区间:类型比较、成员枚举和成员调用。这些区间的系统开销一直在变大。类型比较操作,例如 C# 中的 typeof、GetType、is、IsInstanceOfType 等,都是开销最小的反射 API,尽管它们的实际开销一点也不小。成员枚举操作可以通过编程方式对类的方法、属性、字段、事件、构造函数等进行检查。例如,可能会在设计时的方案中使用这一类的成员枚举操作,在这种情况下,此操作将枚举 Visual Studio 中的 Property Browser(属性浏览器)的 Customs Web Controls(自定义 Web 控件)的属性。那些用于动态调用类成员或动态发出 JIT 并执行某个方法的的反射 API 是开销最大的反射 API。当然,假如需要动态加载程序集、类型实例化以及方法调用,还存在一种后期绑定方案,但是这种松散的耦合关系需要进行明确的性能权衡。一般情况下,应该在对性能影响很大的代码路径中避免使用反射 API。请注重,尽管您没有直接使用反射,但是您使用的 API 可能会使用它。因此,也要注重是否间接使用了反射 API。
提示:假如您正在使用 VB.NET,且并不一定需要后期绑定,您可以在源文件的顶部包含 Option EXPlicit On 和 Option Strict On 以便通知编译器拒绝后期绑定。这些选项将强制您进行声明,并要求您设置变量类型并关闭隐式转换。
安全性
安全性是必要的而且也是主要的 CLR 的组成部分,使用它时会降低性能。当代码为 Fully Trusted(完全信任)且安全策略为默认设置时,安全性对应用程序的吞吐量和启动时间的影响会很小。对代码持不完全信任态度(例如,来自 Internet 或 Intranet 区域的代码)或缩小 MyComputer Grant Set 都将增加安全性的性能开销。
COM 互操作和平台调用
COM 互操作和平台调用会以几乎透明的方式为托管代码提供本机 API,通常调用大多数本机 API 时都不需要任何非凡代码,但是可能需要使用鼠标进行多次单击。正如您所预计的,从托管代码中调用本机代码会带来开销,反之亦然。这笔开销由两部分组成:一部分是固定开销,此开销与在本机代码和托管代码之间进行的转换有关;另一部分是可变开销,此开销与那些可能要用到的参数封送和返回值有关。COM 互操作和平台调用的固定开销在开销中占的比例较小:通常不超过 50 条指令。在各托管类型之间进行封送处理的开销取决于它们在边界两侧的表示形式的相似程度。需要进行大量转换的类型开销相对较大。例如,CLR 中的所有字符串都为 Unicode 字符串。假如要通过平台调用需要 ANSI 字符数组的 Win32 API,则必须缩小该字符串中的每个字符。但是,假如是将托管的整数数组传递到需要本机整数数组的类型中时,就不需要进行封送处理。