编写高性能.net代码

您所在的位置:网站首页 officepage封装代码 编写高性能.net代码

编写高性能.net代码

#编写高性能.net代码| 来源: 网络整理| 查看: 265

哈哈,本章就当了解吧,底层的内容需要自己去单独学习了

托管程序在运行时会加载CLR,CLR会先执行一些封装代码,都是汇编代码。程序集的托管代码在第一次运行时,都会执行一小段调用即时编译器(Just-in-Time,JIT)的“桩”代码(Stub),JIT会把方法的IL转换为硬件汇编指令

●大部分时候,每个方法只需要经过一次JIT编译。但如果方法带有泛型(Generic Type)参数则不一定,这时有可能会对每一种不同类型的参数都调用一次JIT

●如果你的应用程序或者用户很在意第一次JIT编译造成的延时,那么你就需要特别关注一下。大部分应用程序只是关心稳定运行期间的性能,但如果你需要很高的可用性,那么JIT可能会成为一个需要优化的问题

●3.1 JIT编译的好处

●引用的就近访问可能性很高——一起调用的代码常常会存放在同一个内存页中,避免了缺页中断的开销

●内存占用降低——只会对真正用到的方法进行编译

●交叉汇编内联化(Cross-assembly Inlining)——可以把其他DLL中的方法(包括 .NET Framework中的)内嵌到你的应用程序中,以显著提升性能

●可以针对硬件特性进行优化,但在实践中针对特定平台的优化十分有限

●.NET中的大部分代码优化都不是在语言的编译器中实现的(从C#/http://VB.NET到IL),而是发生在JIT编译器的即时编译过程中

●3.2 JIT编译的开销

●JIT在幕后的处理过程

●3.3 JIT编译器优化

●JIT编译器会进行一些标准的代码优化工作,比如方法内联和去除数组范围检查代码

●JIT最大的优化工作就是方法内联

就是把方法体内的代码嵌入调用的位置,避免原先的方法调用。对于那些会被频繁调用的小型函数而言,代码内联比较有意义,因为小型函数的调用开销会大于代码本身的执行开销

●以下这些情况都会阻止方法内联的发生

●虚方法

●同一处调用了接口(Interface)的多种实现代码

●循环

●异常处理函数

●递归

●方法体的IL编码大于32个字节

●3.4 减少JIT编译时间和程序启动时间

JIT编译器关乎性能的另一个主要因素是生成代码所耗费的时间,归根结底最主要还是取决于需要编译的代码量

●特别注意

可能有大量代码是你看不到的,实际执行的代码明显要比源代码中看到的要多得多。对全部隐藏代码进行JIT编译可能需要耗费相当多的时间。特别是正则表达式和代码自动生成,有可能会生成大量重复的代码

●LINQ

LINQ简洁的语法,把执行查询时实际运行的代码数量隐藏了起来。LINQ还把生成委托、分配内存等行为也都隐藏了。简单的LINQ查询也许没有问题,但最重要的一点是你应该进行确切的性能评估

●dynamic关键字

dynamic关键字带来的主要问题,也是因为它会转换为大量的代码

●正则表达式

正则表达式会转换为一个动态程序集中的IL状态机,然后进行JIT编译。这在一开始会多花一些时间,但重复执行时就能节省很多时间

●代码自动生成

●还有JIT之外的因素也会影响启动时间

●比如I/O就会增加启动开销

●PerfView 分析JIT代码桩

●3.5 利用Profile优化JIT编译

Profile文件可被用于代码执行之前的生成过程。Profile的记录过程运行在独立的线程中,保存下来的Profile可以让生成的代码获得与JIT编译相同的就近访问可能性(Locality)。该Profile在程序每次执行时都会自动更新

●启用Profile

ProfileOptimization.SetProfileRoot(@”C:\MyAppProfile”);ProfileOptimization.StartProfile(“default”);

●3.6 使用NGEN的时机(简略)

NGEN把IL汇编代码转换为本机映像,实际上就是运行JIT编译器并把编译结果保存到本机映像程序集缓存目录中

●NGEN一般是作为最后的手段来使用

●第一个缺点就是丧失了对象引用的就近访问可能性

●你可能还失去了某些优化效果,比如交叉汇编的内联化

●如果应用程序的启动(“预热”)开销太高,上一节所述的Profile优化效果也无法满足性能需求,可能就该轮到NGEN上场了

●决定使用NGEN之前,请牢记性能优化的基本指导原则是:评估、评估、再评估

●3.6.2 本机代码生成

●3.7 JIT无法胜任的场合

●比如有一些处理器指令JIT是不会去使用的,即便当前的处理器能够支持也不会。数量最多的就是大部分SSE和SIMD指令

●JIT编译器另一个不如本机代码编译器的地方,就是托管数组与直接访问本机内存的对比

直接访问本机内存通常意味着无需复制内存,而托管代码却是需要封送(Marshalling)的。虽然有办法绕过内存复制,比如用UnmanagedMemoryStream把本机缓冲区封装为Stream,但这样实际上你是在做不安全的内存访问

●果应用程序要执行大量的数组或矩阵计算,你就不得不在性能和安全性之间做出平衡

●如果你真的发现本机代码的效率要更高一些,可以尝试用P/Invoke把所有数据封送给本机代码编写的函数,把计算交给高度优化的C++ DLL完成,然后把结果返回给托管代码

●3.8 评估

●3.8.1 性能计数器

●# of IL Bytes Jitted

●# of Methods Jitted

●%Time in Jit

●IL Bytes Jitted / sec

●Standard Jit Failures

●Total # of IL Bytes Jitted(和“# of IL Bytes Jitted”完全一样)

●%Time Loading

●Bytes in Loader Heap

●Total Assemblies

●Total Classes Loaded

●3.8.2 ETW事件

利用ETW事件,对每一个JIT编译过的方法,你都可以得到大量详细的性能数据,包括IL代码大小和JIT编译耗时

●MethodID——该方法的唯一ID。

●ModuleID——该方法所属模块(Module)的唯一ID。

●MethodILSize——该方法IL代码的大小。

●MethodNameSpace——与该方法关联的完整命名空间名称。

●MethodName——方法名称。

●MethodSignature——方法的签名,以逗号分隔的类型名称列表

●MethodFlags:

●◎0x1——动态方法。

●◎0x2——泛型方法。

●◎0x4——经JIT编译的方法(否则为NGEN处理过的方法)。

●◎0x8——JIT助手方法。

●3.8.3 找出JIT耗时最长的方法和模块

JIT的耗时通常与方法中的IL指令数量直接相关,但由于类型加载时间也可能包含在JIT耗时中,问题就变得复杂了,特别是当模块被第一次用到时

●3.9 小结

●请认真考虑那些有可能大量自动生成的代码

●Profile优化可以对绝大部分使用到的代码进行并行JIT编译,可用于减少应用程序的启动时间

●如果要从函数内联中获得性能收益,请避免用到虚方法、循环、异常处理、递归和代码较多的方法体

●对于大型应用,或者对程序启动阶段的JIT开销无法容忍,可以考虑使用NGEN,在使用NGEN之前,请用MPGO对本机映像进行优化

本文使用 文章同步助手 同步


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3