分类: Android平台
2014-09-02 11:20:18
本文提供了一些性能优化的建议,这些经验来自于使用托管代码重写C# 和 VB编译器,并以编写C# 编译器中的一些真实场景作为例子来展示这些优化经验。.NET 平台开发应用程序具有极高的生产力。.NET 平台上强大安全的编程语言以及丰富的类库,使得开发应用变得卓有成效。但是能力越大责任越大。我们应该使用.NET框架的强大能力,但同时如果我们需要处 理大量的数据比如文件或者数据库也需要准备对我们的代码进行调优。
为什么来自新的编译器的性能优化经验也适用于您的应用程序
微软使用托管代码重写了C#和Visual Basic的编译器,并提供了一些列新的API来进行代码建模和分析、开发编译工具,使得Visual Studio具有更加丰富的代码感知的编程体验。重写编译器,并且在新的编译器上开发Visual Studio的经验使得我们获得了非常有用的性能优化经验,这些经验也能用于大型的.NET应用,或者一些需要处理大量数据的APP上。你不需要了解编译 器,也能够从C#编译器的例子中得出这些见解。
Visual Studio使用了编译器的API来实现了强大的智能感知(Intellisense)功能,如代码关键字着色,语法填充列表,错误波浪线提示,参数提 示,代码问题及修改建议等,这些功能深受开发者欢迎。Visual Studio在开发者输入或者修改代码的时候,会动态的编译代码来获得对代码的分析和提示。
当用户和App进行交互的时候,通常希望软件具有好的响应性。输入或者执行命令的时候,鼎尚娱乐应用程序界面不应该被阻塞。帮助或者提示能够迅速显示出来或者 当用户继续输入的时候停止提示。现在的App应该避免在执行长时间计算的时候阻塞UI线程从而让用户感觉程序不够流畅。
想了解更多关于新的编译器的信息,可以访问.NET Compiler Platform ("Roslyn")
基本要领
在对.NET 进行性能调优以及开发具有良好响应性的应用程序的时候,请考虑以下这些基本要领:
要领一:不要过早优化
编写代码比想象中的要复杂的多,代码需要维护,调试及优化性能。一个有经验的程序员,通常会对自然而然的提出解决问题的方法并编写高效的代码。 但是有时候也可能会陷入过早优化代码的问题中。比如,有时候使用一个简单的数组就够了,非要优化成使用哈希表,有时候简单的重新计算一下可以,非要使用复杂的可能导致内存泄漏的缓存。发现问题时,应该首先测试性能问题然后再分析代码。
要领二:没有评测,便是猜测
剖析和测量不会撒谎。测评可以显示CPU是否满负荷运转或者是存在磁盘I/O阻塞。测评会告诉你应用程序分配了什么样的以及多大的内存,以及是否CPU花费了很多时间在 垃圾回收上。
应该为关键的用户体验或者场景设置性能目标,并且编写测试来测量性能。通过使用科学的方法来分析性能不达标的原因的步骤如下:使用测评报告来指导,假 设可能出现的情况,并且编写实验代码或者修改代码来验证我们的假设或者修正。如果我们设置了基本的性能指标并且经常测试,就能够避免一些改变导致性能的回 退(regression),这样就能够避免我们浪费时间在一些不必要的改动中。
要领三:好工具很重要
好的工具能够让我们能够快速的定位到影响性能的最大因素(CPU,内存,磁盘)并且能够帮助我们定位产生这些瓶颈的代码。微软已经发布了很多性能测试工具比如:Visual Studio Profiler, Windows Phone Analysis Tool, 以及 PerfView.
PerfView是一款免费且性能强大的工具,他主要关注影响性能的一些深层次的问题(磁盘 I/O,GC 事件,内存),后面会展示这方面的例子。我们能够抓取性能相关的Event Tracing for Windows(ETW) 事件并能以应用程序,进程,堆栈,线程的尺度查看这些信息。PerfView能够展示应用程序分配了多少,以及分配了何种内存以及应用程序中的函数以及调 用堆栈对内存分配的贡献。这些方面的细节,您可以查看随工具下载发布的关于PerfView的非常详细的帮助,Demo以及视频教程(比如 Channel9上的视频教程)
要领四:所有的都与内存分配相关
你可能会想,编写响应及时的基于.NET的应用程序关键在于采用好的算法,比如使用快速排序替代冒泡排序,但是实际情况并不是这样。编写一个响应良好的app的最大因素在于内存分配,特别是当app非常大或者处理大量数据的时候。
在使用新的编译器API开发响应良好的IDE的实践中,大部分工作都花在了如何避免开辟内存以及管理缓存策略。PerfView追踪显示新的 C# 和VB编译器的性能基本上和CPU的性能瓶颈没有关系。编译器在读入成百上千甚至上万行代码,读入元数据活着产生编译好的代码,这些操作其实都是I/O bound 密集型。UI线程的延迟几乎全部都是由于垃圾回收导致的。.NET框架对垃圾回收的性能已经进行过高度优化,他能够在应用程序代码执行的时候并行的执行垃 圾回收的大部分操作。但是,单个内存分配操作有可能会触发一次昂贵的垃圾回收操作,这样GC会暂时挂起所有线程来进行垃圾回收(比如 Generation 2型的垃圾回收)
常见的内存分配以及例子
这部分的例子虽然背后关于内存分配的地方很少。但是,如果一个大的应用程序执行足够多的这些小的会导致内存分配的表达式,那么这些表达式会导致几百 M,甚至几G的内存分配。比如,在性能测试团队把问题定位到输入场景之前,一分钟的测试模拟开发者在编译器里面编写代码会分配几G的内存。
装箱
装箱发 生在当通常分配在线程栈上或者数据结构中的值类型,或者临时的值需要被包装到对象中的时候(比如分配一个对象来存放数据,活着返回一个指针给一个 Object对象)。.NET框架由于方法的签名或者类型的分配位置,有些时候会自动对值类型进行装箱。将值类型包装为引用类型会产生内存分配。.NET 框架及语言会尽量避免不必要的装箱,但是有时候在我们没有注意到的时候会产生装箱操作。奥彩过多的装箱操作会在应用程序中分配成M上G的内存,这就意味着垃圾 回收的更加频繁,也会花更长时间。
在PerfView中查看装箱操作,只需要开启一个追踪(trace),然后查看应用程序名字下面的GC Heap Alloc 项(记住,PerfView会报告所有的进程的资源分配情况),如果在分配相中看到了一些诸如System.Int32和System.Char的值类 型,那么就发生了装箱。选择一个类型,就会显示调用栈以及发生装箱的操作的函数。