UGUI最佳实践(2)-UGUI性能分析工具

性能分析有很多可用的工具,下面是常用的几种:

  1. Unity Profiler
  2. Unity Frame Debugger
  3. Xcode Instruments
  4. Xcode Frame Debugger

Unity Profiler

Unity Profiler主要用来进行比较分析:在Unity Profiler运行的时候,通过启用、禁用一些 UI 元素,如果发现波形的振幅迅速变小,那么说明这部分的UI可能存在一定的性能问题。

为了分析这种情况,观察Unity Profiler 中的Canvas.BuildBatchCanvas.SendWillRenderCanvases 行。
Unity Profiler

Canvas.BuildBatch 是本机代码调用,主要是Canvas的批处理构建操作,可以参考前文的描述。
Canvas.SendWillRenderCanvases 包含对 Canvas.willRenderCanvas 事件订阅者的调用,UGUI 的 CanvasUpdateRegistry 类接收该事件,并且进行重构操作,所有被标记为 dirty 的组件,它们的 CanvasRenderer 将在此时被更新。

提示:为了能够更贱方便的观察性能变化,除了 “Renderer”、“Scripts”、“UI” 之外,其他的分类可以禁用掉,通过点击分类前面的小方块可以完成禁用,通过上下拖动各个分类也可以进行排序。

UI分类是在2017.1之后才添加的,不幸的是,部分UI更新操作的分类并不正确,所以在观察 UI 曲线的时候需要非常小心,因为它可能包括相关的UI 调用,例如,Canvas.SendWillRenderCanvases 的分类是 “UI”,但是 Canvas.BuildBatch 的分类是 “Others”、“Renderer”。

在2017.1之后,同时还新增了 UI Profiler,它包含两条时间线和一个 Batch 视窗。

第一个时间线按照“Layout”、“Render”两个分类来展示 CPU 的消耗,这个也存在上述问题,有些方法没有进行说明。
第二个时间线显示了Batch的个数、定点数量以及事件标记,在上面的截图中,可以看到在时间线上有几个按钮点击事件,这些标记可以帮助你判断是什么导致 CPU 出现尖峰。

最后,UI Profiler 提供的最有用的功能是 Batch 视窗,在视窗的左边,通过树形视图显示每个 Canvas以及他们声称的 Batch,数据列包含了一些关于Batch 和 Canvas 的有用信息,理解 Batch Breaking Reason(中断批处理的原因) 的含义对于更好的优化 UI 性能至关重要。

Canvas 在进行 Batch 的时候,可能会由于某些原因而停止,Batch Breaking Reason列指明了这个原因。其中最常见的原因是使用了不同的贴图、不同的材质,在多数情况下,该问题可以通过使用 Sprite Atlas 来解决,最后一列显示了与该 Batch 相关的一些物体,可以通过双击该物体来选择物体。

从2017.3开始,Batch 视窗只能在 Editor 模式下工作,通常Batch 在各个设备上是相同的,所以这依然是很有用的,如果你怀疑在不同的设备上 Batch 结果不一样,可以使用 Frame Debugger 来做进一步判断。

Unity Frame Debugger

对于减少由 UGUI 产生的drawcall,Unity Frame Debugger 是一个非常有用的工具,可以在Windwos->Frame Debugger下打开该工具,当启用时,它将显示所有由 Unity 产生的 drawcall,当然也包括UGUI产生的。

UGUI的 drawcall 根据渲染模式的不同,所在的位置也有所不同:
Screen Space - Overlay 模式时,将会出现在 Canvas.RenderOverlays 分组。
Screen Space - Camera 模式时,将会出现在所选相机的 Camera.Render 分组,作为一个 Render.TransparentGeometry 子组。
World Space渲染模式时,将会作为一个 Render.TransparentGeometry 子组,出现在每个可以观察到该 Canvas 的相机下。

通过“Shader:UI/Default”可以找到各个UI对象(假如没有使用自定义Shader的情况下),如下图:

通过这一组信息,可以很容易将 Canvas 的批处理能力放到最大化,最常见的与设计相关的中断批处理现象就是,不小心将UI进行重叠。

由于 UGUI 的渲染完全在透明队列中,如果在四边形A上有不能合批的其他四边形B,那么A就必须在B之前被绘制,因此A不能和在B之上的四边形C进行合批。

举个列子,假设有三个相互重叠的四边形A,B,C,其中A、C使用相同的材质,B使用不同的材质,因此四边形B不能和A、C进行合批。在布局UI时,从上到下它们的顺序是A、B、C,那么A和C是不能进行合批的,因为B必须在A之前、C之后进行绘制。然而,如果B放在A、C之前或者之后,那么A和C就可以进行合批。

更加详细的内容,会在后续章节进行研究。

结果分析

当收集到分析数据之后,可能会得出一些结论,如果 Canvas.BuildBatch 或者 Canvas.UpdateBatches 占用了大量的CPU 时间,那么一个可能的问题是,一个Canvas 包含了过多的 CanvasRederer 组件,可以参考分割画布部分内容解决该问题。

如果 GPU 消耗了大量的时间去渲染 UI,并且 Frame Debugger 表明片段管线是性能的瓶颈,那么可能的原因是 UI 的像素填充率(pixel fill rate)超过了GPU 的处理能力,最有可能是出现了 UI 透支,可以参考修复填充率问题部分内容。

如果 Graphic 重构消耗了大量的 CPU 时间,大量时间用来执行 Canvas.SendWillRenderCanvasesCanvas::SendWillRenderCanvases,此时需要进一步分析该问题,Graphic 重构过程中的某些步骤可能存在问题。

如果大量大时间花费在了 WillRenderCanvas 下的 IndexedSet_Sort 或者 CanvasUpdateRegistry_SortLayoutList,那么时间是用来排序脏的Layout 组件列表。此时可以考虑减少Cavans下的Layout 组件。参考使用RectTransform 替代 Layout分隔画布中的内容来解决该问题。

如果大量的时间花费在了 Text_OnPopulateMesh,那么元凶就是文本网格的生成,参考Best Fit禁用画布中的内容来解决问题,如果存在大量的 Text 被重构,但实际文本内容却没变化,此时可以考虑分隔画布中的一些建议。

如果大量的时间花费在了 Shadow_ModifyMesh 或者 Outline_ModifyMesh(或者其他的一些 ModifyMesh 实现),那么问题在于花费了大量的时间来计算网格修改器,考虑删除这些组件,然后通过静态图片实现这些效果。

如果在 Canvas.SendWillRenderCanvases 没有发现特定的性能热点,或者他好像每一帧都在执行,那么问题可能是将动态元素和静态元素分到同一个组中,这会强制整个 Canvas 重构非常频繁。参考画布分隔部分内容进行解决。