编写高性能.net代码 |
您所在的位置:网站首页 › officepage封装代码 › 编写高性能.net代码 |
哈哈,本章就当了解吧,底层的内容需要自己去单独学习了 托管程序在运行时会加载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 |