WPF程序性能优化
WPF(Windows Presentation Foundation)是微软推出的基于Windows的用户界面框架,运行在 .NET Framework 3.0及以上版本。WPF是基于DirectX引擎的,支持GPU硬件加速,在不支持硬件加速时也可以使用软件绘制。尽管WPF有诸多优点,有时我们还是会遇到性能问题,比如界面卡顿,内存泄漏等等。针对WPF程序的性能优化是一个宽泛的问题,本文是对我们这段时间以来所作工作的一个总结。
图形硬件
相同的程序在不同的硬件上运行,会有不同的表现。对渲染能力影响比较大的硬件特性为:
- Video RAM - 现存的数量决定合成图形的缓冲区的大小和数量。
- Pixel Shader - 像素着色器决定了像素计算能力。根据图形的分辨率不同,每帧可能需要处理几百万个像素。
- Vertex Shader - 顶点着色器用于计算图形的顶点数据。
- Multitexture Support - 多重纹理支持是指在3D图形的混合操作中应用两个或更多不同纹理的能力。
代码规范
在程序开发过程中制定一些代码规范,可以让我们避免一些性能问题的坑。
1.让 VisualTree 尽量简单
在屏幕上绘制图形时,每个元素的布局被调用两次(measure & arrange)。布局过程是一个数学密集型的过程,子元素的数量越多,需要的计算次数就越多。
2.虚拟化你的 ItemControls
ItemsControls往往会增加 VisualTree 的深度,如果没有虚拟化会频繁地创建和销毁元素。使用 VirtualizingStackPanel 作为宿主,并设置 VirtualizationMode=Recycling 以便重用元素的容器。
3.尽可能使用 StaticResources
DynamicResources 会创建一个临时表达式并推迟对资源的查找,当真正需要资源值时才去请求,其查找方式和运行时查找相同,这会对性能产生影响。
4.通过 Brushes 设置透明度
如果使用 Brush 来填充元素,那么最好设置 Brush 的透明度而不是设置 Element 的透明度。当修改 Element 的透明度时会导致WPF重绘。
5.避免使用 run 来设置Text属性
<TextBlock>
<Run Text="F"/>
<Run Text="W"/>
</TextBlock>
6.StreamGeometry对象比PathGeometry更轻量级
StreamGeometry在处理多个PathGeometry对象时进行了优化,消耗更小的内存,有更高的性能表现。
7.使用缩小尺寸的图像
如果程序需要显示更小的缩略图,那么创建缩小尺寸的图像版本会提高性能。
8.BitMapScalingMode
默认情况下,WPF使用高质量的图像重采集算法,会导致帧率下降和动画断断续续。设置 LowQuality 切换到速度优化算法可以提高性能。
9.Freeze Freezables
可冻结对象是一种特殊类型的对象,具有两种状态:未冻结和已冻结。比如 Brush,Geometry,冻结对象可以提高性能并减少内存消耗。
10.修正绑定错误
绑定错误是WPF程序中最常见的性能问题原因,每次发生绑定错误都会执行一次 perf 命中并尝试解决错误。
11.避免绑定到 Label.Content
把string绑定到 Label.Content时会导致较差的性能。每次数据变动时,旧的string对象会被丢弃并创建一个新的string对象。可以替换为 TextBlock.Text 属性。
12.使用 IList 作为 ItemsControls 数据源
当绑定 IEnumerable数据作为 ItemsControl数据源时,WPF会重新包装为 IList 类型。
13.UI线程只做简单的事情
不要在UI线程上做耗时的动作,UI线程只用来更新界面。
14.降低动画的帧率
通过设置 Storyboard.DesiredFrameRate 降低动画的帧率。
15.内存泄漏
内存泄漏也会降低程序性能,这块内容比较多,后面会单独篇幅中介绍。
性能分析工具
当我们的程序开发完成后仍然存在性能问题,这时就要用到性能分析工具来帮助我们分析应用程序的运行时行为,并确定可以应用的性能优化的类型。比较常用的工具有:
- Snoop - 可以查看可视化对象的数量
- WPFPerf - WPF提供的套件
- Ants Performance Profiler
- dotTrace - 函数运行时长
- dotMemory - 内存占用情况
不同的软件,使用方法和侧重点会有区别,可以真实使用并分析总结一下。下面对 dotTrace 做一个简单的介绍。
dotTrace
运行程序,打开dotTrace,我们就可以把进程附加到dotTrace上。运行一段时间,获取运行时情况快照来分析具体耗时情况。
如果是调试程序,还可以联动代码,非常的方便。
找到耗时较长的代码,我们就可以针对性的对某个方法进行优化。比如针对一个通过反射动态获取字段的代码块,优化前后的对比如下:
比如一个 string.Split() 方法的优化:
string.Split() 方法内部会自动转换成 char[] 类型,如果能直接传入 char[] 类型会减少方法的耗时。
像上面对方法的优化还有很多,我们最好对照 dotTrace 报表为每一个耗时方法都去分析为什么耗时?还可以怎么优化?
UI线程
在上面的分析做完之后,我们发现有个窗口还是占用比较高的耗时和CPU。这是一个第三方的图表控件 DevExpress ,用来刷新波动率图像。在尝试优化使用方法后,还是决定自己封装插件进行图像的绘制。我们把图像上所用到的元素分类抽象出来:
使用dotTrace来分析对比两个控件的性能:
DevExpress拥有.NET开发需要的平台控件,包含600多个UI控件、报表平台等一系列辅助工具,可为桌面、Web和移动应用提供直观的解决方案。
内存泄漏
dotMemory 是一个 .NET 的内存分析工具,可以帮助你优化 .NET 应用的内存使用,找出内存泄漏和其他的内存使用问题.
内存泄漏是一个内容比较多的主题,我们会有另外一篇文章具体来总结这方面的工作。最简单使用场景:
- 可以对内存占用比较多的模块进行优化
- 关闭窗口,查看内存是否仍然存在
- 程序正常运行一段时间,查看内存变化情况
程序性能的优化是一个长周期的工作,我们也会持续的进行。本次比较笼统的总结了我们这段时间以来的工作,希望对你有所帮助。