【unity】使用Profiler进行性能分析

在游戏开发过程中,游戏性能是非常重要的,学会使用unity自带的profiler工具是非常必要的,以下是我从官方地址找到的进阶教程,感觉文章非常详细,于是尝试翻译。

官方教学文档翻译,英文教程地址https://unity3d.com/cn/learn/tutorials/temas/performance-optimization/diagnosing-performance-problems-using-profiler-window?playlist=44069

介绍

如果我们游戏运行很慢,卡顿甚至卡死,我们就知道游戏出现了性能问题。在我们尝试修复问题之前,我们首先要知道是什么造成了这种问题。不同的问题需要不同的解决方案。如果我们尝试猜测问题或根据其他项目对游戏进行调整,这会非常浪费时间甚至会使问题变得更加糟糕。

这个时候,我们就需要对问题进行分析。分析是在运行我们游戏的时候对各个方面进行测量。使用profiling工具,当我们游戏运行的时候,可以看到屏幕后面发生的事情并且根据这些信息跟踪造成性能问题的原因。通过查看profiling工具,我们可以测量我们修改后的结果,这样我们就可以判断我们的修复是否有效。

在这篇文章中,我们将:

  • 使用Unity自带的的Profiler工具去收集我们游戏性能差的游戏的数据。
  • 分析这些数据并且使用分析的结构去追踪到性能问题
  • 提供修复这些问题的链接

要让一个游戏运行顺畅是一个平衡的过程。在获得理想结果之前,我们可能要对游戏进行好几次的修改和验证。知道如何使用profiling工具去分析我们的问题意味着我们能够确定游戏的问题是什么并且知道下一步要怎么做。

写在开始

这篇文章会帮助我们跟踪到造成Unity游戏运行缓慢、卡顿甚至卡死的位置。如果我们有其他问题,比如崩溃或图像异常,这篇文章可能不会有太大的帮助。如果我们游戏中出现了这篇文章没所提到的一些问题,可以尝试搜索Unity手册、Unity社区或Unity解答。

如果我们对Profiler窗口或如何使用Profiler不熟悉的话,建议先看这篇文章

对游戏性能的一个简介

帧率是衡量游戏性能的标准。游戏里面的帧跟动画的帧类似。只是游戏里面的图像被画到了屏幕。画一帧到屏幕被称为渲染一帧。帧率或帧被渲染的速度以每秒来衡量(FPS)

现在大多数游戏都是以60FPS为目标。通常30FPS以上被认为是可以接受的,特别是对于一些对反应速度要求不高的游戏,如解谜或冒险游戏。一些游戏对帧率要求比较高,如VR,90FPS都会被嫌弃。帧率在30FPS以下,玩家体验通常会比较差,图像可能会卡顿、操作起来也很迟钝。然而,不仅仅速度重要,帧率稳定也很重要。帧率发生变对对玩家来说是很明显的。不稳定的帧率通常比稳定但是帧率低的游戏更糟糕。

尽管帧率是谈论游戏性能经常提到,但是要尝试去改善游戏性能的时候考虑渲染一帧所需要的毫秒数会更有用。有两个原因,首先这种一种更精准的测量方法。当我们尝试改善我们游戏性能的时候,每毫秒都能达到我们的目标。其次,帧率的相对变化意味着不同的规模帧率也是不一样的。从60FPS50FPS代表处理时间增加了3.3毫秒,但是如果从30FPS20FPS代表处理时间增加了16.6毫秒。同样是降低了10FPS,但是渲染一帧所花费的时间是截然不同的。

了解普遍帧率渲染一帧所需要花费多少毫秒是非常有帮助的。要找到这个花费的时间,我们应该遵循这个公式1000/[渴望的帧率]。使用这个公式,我们可以知道每秒渲染30FPS,那么渲染每帧花费在33.3毫秒以内。一个游戏要运行到60FPS, 那么渲染每帧花费在16.6毫秒以内。

对于渲染的每一帧,Unity都必须要执行很多不同的任务。简单来说,Unity必须更新游戏的状态,拿到游戏的快照并且渲染到屏幕。每帧必须要执行的任务包括读取用户输入、执行脚本、灯光运算。除此之外,还有一些一帧内执行多次的操作,如物理计算。当所有的任务执行的足够快,我们的游戏将会有一个一致性的,可接受的帧率。当所有的任务执行得不够快,会花费更长的时间去渲染,并且帧率会下降。

知道哪个任务执行时间长,对如何解决游戏性能问题是至关重要的。一旦我们知道哪个任务在减低帧率,我们可以尝试优化那部分内容。这就是为什么分析如此重要:profiling工具可以显示在给定的帧在每个任务花费多长时间。

记录分析数据

为了研究我们的游戏性能,我们必须记录游戏中性能不佳的数据。为了获得更加精准的分析数据,我们要打一个测试包运行在目标硬件上,并且记录分析数据。

如果我们还不熟悉打包和真机记录分析数据,点击这里查看操作指南

记录游戏数据

  • 使用development build方式打包,在目标机上运行
  • 在我们达到有性能问题之前,开始记录分析数据
  • 一旦我们记录的分析数据包含了性能问题的例子,点击Profiler窗口上方任意位置以暂停游戏并选择一帧
  • Profiler窗口的上方,选择显示性能较差的帧。这可能是低于我们要求帧率的“尖峰”或是有代表性的帧。我们可以使用左右按键或前进后退键在帧之间更好的移动。

我们已经获取游戏中性能较差的分析数据。下一步,让我们学习如何分析这些数据。

分析数据

在得出任何关于游戏性能结论之前,我们必须学习如何阅读和分析显示在Profiler窗口的性能数据。我们知道,当Unity无法及时的完成渲染所需要的所有任务时,帧率会下降。我们将会使用Profiler窗口查看什么任务被执行了,任务花费多长时间以及按什么顺序执行的。这些信息会帮助会帮助我们明白我们游戏的哪些部分会造成任务花费太长的时间去渲染。

最好去学习如何分析而不是学习一系列的步骤。自己理解这些数据更有用,这样当我们遇到了新问题的时候可以自己去研究。即使我们只是学会了在Unity解答上搜索,这也是一个伟大的开始。

为了学习如何分析,我们将会使用CPU分析器作为例子,这可能是我们在研究帧率问题上用的最多的分析器。

CPU分析器

当我们在Profiler窗口看CPU分析器的时候,我们可以看到CPU完成每帧所花费的时间。

我们可以看到时间花费的彩色浪图。不同的颜色代表时间花在渲染操作上,物理计算上等等。那些关键字标明哪些颜色代表哪些任务。

在接下来的截图中,我们可以到这一帧的主要时间花费在渲染操作上。下方的CPU时间指示器表明了我们的总的CPU时间在这一帧花费了85.95毫秒。

层次结构图

让我们使用CPU分析器的层次结构视图去深挖当前数据并且更精确的查看当前帧哪一个任务花费CPU时间最多。当选中CPU分析器的时候,我们可以在Profiler窗口的下半屏看到当前帧的详细信息。查看Profiler窗口的下半屏,我们在做上方可以使用下拉菜单选择结构视图。这可以让我们看到CPU上正在发生的任务的详细信息。


在层次结构图中,点击任何列的列头按该值排序。比如,点击Time ms按花费时间最长开始排序,点击Calls按当前高亮的帧调用次数最多的函数排序。在以上截图中,我们按照耗时排序,我们可以看到CPU最耗时的函数是Camera.Render。

如果一个函数名字的左右有小箭头,我们可以展开看到这个函数调用了其他哪些函数和他们的性能影响。Self ms列表明这个函数自己的耗时,Time me列表明这个函数和它调用的其他函数的耗时。


在这种情况下,我们可以看到Camera.Render下,最耗时的函数是Shadows.RenderJob。即使我们对这个具体的函数还不太了解,但是我们已经有关于我们游戏问题的信息了。我们知道我们的问题跟渲染有关,这个当前最耗时的任务是shaodws有关。

我们可以在层次结构图做的另一个有用的事是比较我们游戏的帧,这样我们可以明白性能是如何随着时间的变化而变化的。我们使用CPU分析器一帧一帧的分析出单个最耗时的函数。当我们点击CPU分析器层级结构图上的函数名的时候,函数相关数据会高亮。

比如,我们在层级结构视图点击Gfx.WaitForPresent,跟Gfx.WaitForPresent相关的渲染函数会高亮显示。

时间线视图

现在让我们使用CPU分析器的时间视图学习更多关于我们渲染的问题。时间视图显示了两个东西:CPU任务执行的顺序和哪个线程负责哪个任务。我们可以在Profiler窗口的下半屏的左上角使用下拉按钮选择时间线视图(那里之前显示的是结构视图选项)

线程可以同时运行多个单独任务。当一个线程执行任务的时,另一个线程可以执行完全独立的任务。Unity的渲染处理包含三种类型的线程:main thread、render thread和worker threads。了解哪些线程负责哪些任务是非常有帮助的:一旦我们知道了哪个线程执行了的任务最慢,我们就明白应该集中精力去优化那些线程的操作。

我们可以放大时间线视图来更仔细的查看单个任务。被调用的函数也会在调用者下方立即显示出来。在这个例子中,我们已经放大了Shadows.RenderJob看到了组成这个任务的单个任务。我们可以看到Shadows.RenderJob是在main thread调用的。我们同样看到workder threads执行了跟shadows相关的任务。主线程列出了一个叫WaitingForJob的任务,表明main thread正在等待worker thread完成任务。从这我们可以得出结论,shadows相关的渲染操作在main thread和worker threads花费太长时间了。我们现在知道问题所在了。

其他分析器

尽管在跟踪与帧率相关的性能问题时,CPU分析器是最常用的工具,其他分析器也同样非常有用。熟悉他们提供的信息是一个不错的主意。

按照上面的步骤,尝试学习其他几个不同的分析器每帧提供了什么信息。比如,尝试使用渲染分析器,了解不同帧的渲染统计数据是如何变化的。

确定造成性能问题的原因

既然我们熟悉了在分析器中读取和分析性能数据的过程,我可以开始找到造成性能问题的原因了。

排除垂直同步

垂直同步简称VSync,用来匹配游戏帧率与屏幕刷新速度。垂直同步会影响游戏帧率,并且它的影响会显示在Profiler窗口上。如果我们不明确在看什么,还容易认为这是一个性能问题,所以在我们继续研究性能问题之前要学会如何排除掉垂直同步

在CPU分析器中隐藏垂直同步信息

我们可以在CPU分析器中选择要隐藏的信息。这可以让我们忽略对当前研究没有帮助的信息。

隐藏垂直同步的步骤如下:

  • 点击CPU分析器
  • 在Profiler窗口的顶部,CPU分析器区域显示当前关注的数据,点击标记为VSync的黄色正方形就可以隐藏垂直同步的信息

在层级结构视图中无视垂直同步信息

没有办法在层级结构视图中隐藏垂直同步信息,但我们知道了它长什么样子,我们就可以无视它。

无论什么时候,我们在层级结构视图中看到WaitForTargetFPS,这意味着我们的游戏在等待垂直同步,我们不需要研究这个函数,忽略它就好。

屏蔽垂直同步

垂直同步不能在所有的平台上都屏蔽:许多(如IOS)强制使用垂直同步。但如果我们正在为一个不需要强制使用垂直同步的平台开发,我们就可以屏蔽掉垂直同步。点击 Edit -> Project Settings -> Quality,找到VSync Count,下来菜单选中Don't Sync

渲染分析器

渲染是造成性能问题的常见原因。尝试修复渲染问题之前,确认我们的游戏是CPU密集还是GPU密集是很关键的,因为不同的情况,解决方法也不一样。

简单的说,CPU负责决定绘制什么,而GPU负责绘制。如果渲染问题归咎于CPU耗时太多,那游戏就属于CPU密集,如果渲染问题归咎于GPU还是太多,那游戏就属于GPC密集。

辨别我们的游戏是否是GPU密集

辨别我们的游戏是否是GPU密集最快捷的方法是使用GPU分析器。不幸的是,并不是所有的设备或驱动都支持这个分析器。在我们使用GPU分析器之前,我们先检测GPU分析器在目标设备上是否可用。

检测GPU分析器在目标设备上是否可用,我们应该执行一下步骤:

  • 在Profiler窗口左上角,选择Add profiler
  • 通过下拉菜单选择GPU

如果GPU分析器不支持使用,我们会在GPU正常显示数据的区域看到一条以“GPU Profiling is not supported”开头的信息。

如果没有没有看到这条信息,这意味着GPU分析器在目标设备上是支持的。如果GPU分析器是可用的,那执行一下步骤就能非常快速辨别出我们游戏是否是GPU密集:

  • 点击GPU分析器
  • 查看屏幕正中心区域,那里显示了当前选中帧的CPU和GPU耗时。

    如果GPU耗时大于CPU耗时,则我们可以确认我们的游戏是GPU密集。

如果GPU分析器不能在目标设备上使用,我们仍然可以辨别我们的游戏是否是GPU密集。我们可以通过观察CPU分析器。如果我们看到CPU正在等待GPU完成任务,这就是意味着我们游戏是GPU密集。为了查明是否存在这种情况,我们可以执行以下步骤:

  • 点击选择CPU分析器
  • 检查Profiler窗口底部区域显示的当前帧信息和分析器信息
  • 在区域左上角的下拉菜单中选择层级结构视图
  • 选择Time ms列头,函数耗时按时间排序

如果函数Gfx.WaitForPresent是CPU分析器中最好是的函数,这表明CPU正在等待GPU。这意味着我们的游戏是GPU密集。

解决我们游戏是GPU密集时的渲染问题。
如果我们已经确认我们的游戏是GPU密集,我们应该阅读这篇文章

辨别我们的游戏是否是CPU密集

如果我们还没有确认造成性能问题的原因,现在我们研究基于CPU的渲染问题。

  • 点击选择CPU分析器
  • 随着时间的推移,Profiler窗口顶部信息会显示分析数据,检测图像中代表渲染的部分。我们可以通过点击关键字旁边的带颜色的正方形图片显示或隐藏这些数据。

如果慢帧的大部分时间被渲染占用了,这意味着渲染可能是造成我们问题的原因。我们可以按以下步骤继续挖掘来确认:

  • 点击选择CPU分析器
  • 检测Profiler窗口显示的当前帧信息和分析器信息
  • 在分析数据区域左上角下拉菜单中选择层级结构
  • 选择列头Time ms,按函数耗时排序
  • 点击选择最顶部的函数

如果选中的是一个渲染函数,CPU分析器图像将会高亮显示Rendering图像。如果是这种情况,这意味着喧嚷相关的操作是造成我们性能问题的原因,也可以确认我们的游戏是CPU密集。留意函数名和是哪个线程执行这个函数。当我们尝试解决问题的时候,这些信息是很有用的。

解决我们的游戏是CPU密集时的渲染问题。
如果我们已经确认我们的游戏是CPU密集的渲染问题时,我们应该阅读这篇文章

垃圾回收分析器

接下来,我们检查垃圾回收是否会造成瓶颈。垃圾回收是Unity自动内存管理的特性,这可能是一个缓慢的操作。

  • 点击选择CPU分析器
  • 注意,你可以拖拽你感兴趣的部分的名称,重新排序它们,在下面的截图中,我们拖拽GarbageCollector到顶部,并且点击关掉了其他方面的数据。

    如果慢帧的大部分时间被垃圾回收占用了,这指明了我们有垃圾回收过度的问题。我们可以深入研究以确认问题。

  • 点击选择CPU分析器,检测Profiler窗口底部显示的当前帧相信信息
  • 底部区域左上角下拉按钮选择层次结构视图
  • 选择Time ms列头,以函数耗时排序

如果GC.Collect()函数出现,并且耗时比较多,我们就可以确认我们的游戏有垃圾回收问题。

解决垃圾回收问题

如果我们确认我们的额游戏有垃圾回收问题,我们应该阅读这篇文章

物理分析器

如果我们排除了渲染和垃圾会回收问题,我们检查负责的物理计算是否是造成我们性能问题的原因。

  • 点击选择CPU分析器
  • 在Profiler窗口显示数据的顶部,检测代表Physics的图像(橙色图像)。我们通过点击名字旁边的带颜色正方形来显示或隐藏图像。

如果慢帧的大部分时间被物理占用,那么可以确认武林是造成我们问题的原因。我们可以进一步研究以确认问题:

  • 点击选中CPU分析器,检测Profiler下方区域显示的当前帧详细信息。
  • 在底部区域左上角的下拉菜单选择层级结构视图
  • 选择Time ms列头,按函数耗时排序
  • 点击选择顶部的函数

如果选中的是一个物理函数,CPU分析器图像将会高亮显示Physics图像。如果是这种情况,我们可以确定造成性能问题的原因和物理计算相关。

解决物理问题

如果我们确认我们的问题是物理引起,以下资料会有帮助:

运行缓慢的脚本

现在我们检测缓慢的或过度负责的脚本是否是造成性能问题的原因。脚本,这里将的是非Unity引擎代码。这些脚本通常是我们自己写的,或者是第三方插件引入的。

  • 点击选择CPU分析器
  • 在Profiler窗口显示数据的顶部,检测代表Script的图像,我们可以通过点击关键字旁边的颜色方块显示或隐藏图像数据。

如果慢帧大部分时间被scripts占用,那可以确认开发者写的脚本是造成问题的原因。我们可以继续研究确认问题:

  • 点击选中CPU分析器,检测Profiler窗口下方的当前帧详细数据
  • 在底部区域左上角的下拉菜单选择层级结构视图
  • 选择Time ms列头,按函数耗时排序
  • 点击选择顶部的函数

如果是自己写的脚本,CPU分析器图像将会高亮显示Scripts图像。这种情况下,我们可以确认造成性能问题的原因与我们写的脚本有关。

请注意,上面有一种特殊情况:当我们游戏包含渲染相关的函数,如Image Effects脚本或OnWillRenderObjectOnPreCull函数,这些将会出现在渲染分析器而不是脚本分析器。

尽管起初有点小混乱,但是平常使用层级结构视图和时间线视图检测代码的时候,也能够跟踪到相关的代码。

解决缓慢代码问题

如果我们确定自己写的脚本是造成性能问题的原因,这里有一些简单的技巧可以改善性能。下面是一个关于代码优化的资源:

其他造成性能问题的原因

虽然我们已经讨论了性能问题最常见的四个原因,但是我们游戏可能有一些这里没有提到的性能问题。这种情况下,我们应该以上的一些方法来收集数据,研究CPU分析器并且找到造成问题的函数名字。一旦我们知道了函数名字,我们可以通过搜索Unity手册、Unity社区和Unity解答来或者这个函数的一些信息和如何减少这些函数消耗的方法。

希望本文对你有帮助,欢迎关注公众号:hellokazhang,一个不给自己设限的终身学习者。

推荐阅读:

你真的会搜索吗?

谈谈基因编辑

最好的投资是提升自己的能力。

推荐阅读更多精彩内容