首页 > 代码库 > Unity性能优化(2)-官方文档简译

Unity性能优化(2)-官方文档简译

本文是Unity官方教程,性能优化系列的第二篇《Diagnosing performance problems using the Profiler window》的简单翻译。

简介

如果游戏运行缓慢,卡顿,我们知道游戏存在性能问题。在我们尝试解决问题前,需要先知道引起问题的原因。不同问题需要不同的解决方案。如果我们靠猜测或者其他项目的经验去解决问题,那么我们可能会浪费很多时间,甚至使得问题更严重。

这时我们需要性能分析,性能分析程序测量游戏运行时的各个方面性能。通过性能分析工具,我们能够透过游戏运行的场景表面表现,获取深入的信息,通过这些信息,我们可以追踪引起性能问题的原因。通过性能分析工具,当我们修改后,我们可以测量我们的修改是否有效,是否修复了性能问题。

在本文中,我们将会:

-使用Unity的内置性能分析工具Profiler去收集我们的低性能游戏的运行时数据。

-分析数据,并追踪引起性能问题的原因

-分享修复这些特定问题的文章的链接

使得游戏运行快速平滑是一项平衡性的工作。在获取我们想要的结果前,我们可能需要好几轮的修改,并测量效果。知道如何使用性能分析工具分析问题意味着我们能够确认哪里出了问题,并且理解接下来应该怎么样尝试修改。

开始之前

本文将帮助我们追踪定位Unity游戏运行缓慢,卡顿的问题。如果我们的游戏存在其他问题,如游戏崩溃或者游戏的图形表现不如预期所想,那么这篇文章可能不会有什么帮助。如果游戏存在的问题不在本文讨论范围内,请尝试在Unity Manual, Unity Forums or Unity Answers寻求答案。

如果我们还不熟悉Profiler Window的使用,请先阅读Unity性能优化(1)-官方文档简译

关于游戏性能的概要介绍

帧率Frame rate是衡量游戏性能的基本指标。在游戏中,一帧类似于动画中的一帧,它是我们游戏绘制到屏幕上的一个静止画面。绘制一帧到屏幕上叫做渲染一帧。帧率,或者说帧被渲染的有多快,用FPS衡量(frames per second)。

大多数当前游戏的目标是帧率60FPS。通常来说,帧率在30FPS以上是可以接受的,特别是对于不需要快速反应互动的游戏,例如休闲解密冒险游戏等。有些项目有特殊的需求,在VR游戏中,则至少需要90FPS。当帧率降低到30FPS以下时,通常玩家会有不好的体验;图形可能不稳定并且感觉操作反馈不及时。尽管如此,重要的不仅仅是速度,帧率必须也非常稳定。玩家通常对帧率的变化比较敏感,不稳定的帧率通常比低一些但是很稳定的帧率表现更差。

虽然帧率是一个我们谈论游戏性能的基本标准,但是当我们尝试优化我们的游戏性能时,对于我们来说更有用的是渲染一帧需要多少毫秒。这有两个原因。首先,这是一个更精确的度量。当我们尝试优化性能时,每一毫秒计算有助于我们的目标。第二,帧率的相对改变在不同范围意味着不同的变化。从60到50FPS呈现出的是额外3.3毫秒的运行时间,但是从30到20FPS呈现出的是额外的16.6毫秒的运行时间。在这个例子中,同样是降低了10FPS,但是渲染一帧上时间的差别是很显著的。

帧率对于我们来说可以用于理解为渲染一帧必须用多少毫秒以内完成。通过公式 1000/想要达到的帧率。通过这个公式可以得到,游戏要每秒渲染30帧(30FPS),那么必须在33.3毫秒之内渲染完每一帧。一个60FPS运行的游戏,必须在16.6毫秒内渲染完每一帧。

要渲染每一帧,Unity必须执行很多不同的任务。简单的说,Unity必须更新游戏的状态,获取游戏的快照并且把快照画到屏幕上。有一些任务是在每一帧都执行的,包括读取用户输入,执行脚本,运行光照计算等。除此之外,有许多操作是在一帧执行多次的,例如物理运算。当所有这些任务都执行的足够快时,我们的游戏才会有稳定且可接受的帧率。当这些任务执行的不够快时,渲染一帧将花费太长的时间,并且帧率会因此下降。

知道哪些任务花费了太长的时间执行,对于我们知道怎样去解决性能问题是十分关键的。一旦我们知道了哪些任务降低了帧率,我们就可以尝试去优化游戏的那一部分。这就是为什么性能分析工具这么关键:性能分析工具告诉我们在一帧中每个任务花费了多长时间。

录制分析数据

为了调查性能问题,我们必须首先从我们游戏性能差的部分录制分析数据。为了精确的录制分析数据,我们必须生成一个development build版本的游戏,并且当游戏在目标硬件上运行时进行分析。

如果还不熟悉怎么生成development build版本的游戏,并在目标硬件上运行时进行分析,请查看这篇文章Unity性能优化(1)-官方文档简译

从我们游戏的development build录制数据

如果还不熟悉怎么生成development build版本的游戏,并在目标硬件上运行时进行分析,请查看这篇文章Unity性能优化(1)-官方文档简译

-在目标设备上生成development build。

-当我们的游戏运行到性能出现问题的部分时开始录制分析。

-一旦我们录制的数据包含了性能问题的样本时,点击Profiler窗口上部的任意位置暂停游戏,并且选择一帧。

-在窗口上半部分,选择展示出性能问题的那一帧。这可能是突然的低帧数,也可能仅仅是持续的平常的帧数,只是比我们希望的帧数低。我们可以使用键盘的左右箭头按键或者Profiler上部控制栏的前后按钮移动帧,更好的控制选择帧。

技术分享

我们已经收集到了游戏性能不好的部分的性能数据,接下来让我们学习怎么去分析这些数据。

分析性能数据

在我们得到引起游戏性能问题的原因之前,我们必须先学习怎么样去分析显示在Profiler窗口上的性能数据。我们知道,当Unity无法及时的完成渲染一帧所需的全部任务时,会发生帧率的下降。我们可以在Profiler窗口中看到哪些任务正在执行,执行任务花费的时间,以及任务执行的顺序。这些信息将帮助我们理解,游戏的哪部分在渲染帧时花费了太长的时间。

最好是实践使用Profiler,比尝试去按照精确的顺序学习要好的多。我们能够自己理解数据的意义是很有用的,这样当我们遇到新问题时就可以自己调查。或者仅仅是知道在Unity Answers上怎么查找,也是一个好的开始。

为了学习怎么去分析性能,我们将使用CPU usage profiler作为例子。当我们调查帧率问题时,这个可能是我们使用的最多的Profiler了。

The CPU usage profiler

当我们查看Profiler窗口的上部时,可以看到完成每帧的任务花费了多少cpu时间。

技术分享

我们可以看见花费的时间用颜色标出了分类。不同的颜色表示在渲染操作上花费的时间,物理运算花费的时间等等。Profiler左侧表明了哪种颜色代表哪类任务。

通过下面的截图我们可以看出,这帧中主要的时间花费在渲染操作上。在窗口底部显示了这帧中所有的cpu时间共消耗85.95毫秒。

技术分享

层级视图The Hierarchy view

我们使用层级视图去挖掘更深入的信息,看看在这帧中到底是哪些任务花费了最多的cpu时间。当我们选中CPU usage profiler时,这一帧的详细信息会显示在Profiler窗口的下部。我们可以在Profiler的左下的下拉菜单中选择层级视图,这可以使我们查看cpu任务的详细信息。

技术分享

层级视图中,可以点击任意的列标题,按照这列信息的值来列排序。例如,点击Time ms可以按照函数花费时间排序,点击Calls可以按照当前选中帧中函数的执行次数排序。在上面的截图中,我们按照时间排序,可以看到Camera.Render花费了最多的cpu时间。

如果一个函数的名字左边有箭头,则可以点击展开,查看这个函数调用了哪些其他函数,并且这些函数是怎样影响性能的。Self ms表示函数自身的花费时间,Time ms表示这个函数以及它所调用的其他函数的总时间。

技术分享

在这个例子中,我们可以看到,在Camera.Render,最多的消耗和Shadows.RenderJob函数相关。即使我们对这个函数还不太了解,我们也已经对性能问题有了很多信息了。我们知道了问题是和渲染相关的,并且最昂贵的任务是需要处理阴影。

在层级视图中,另一个好处是我们可以比较游戏中的每一帧,这样我们就可以理解性能是怎样随着时间变化的。使用CPU usage profiler,我们可以跟踪一个函数从帧到帧的cpu消耗。当我们在层级视图中点击函数名字时,CPU usage profiler将在Profiler窗口上部的图形视图中高亮显示这个函数的信息。

技术分享

举个例子,如果在层级视图中点击了Gfx.WaitForPresent,则和Gfx.WaitForPresent直接相关的渲染数据将会在Profiler的图形展示中高亮显示。

时间线视图The Timeline view

现在我们使用时间线视图来学习更多关于我们的渲染问题的信息。时间线视图显示了两件事:cpu任务的执行顺序和那个线程负责哪些任务。我们可以Profiler的左下的下拉菜单中选择时间线视图(和选择层级视图位置相同)。

技术分享

线程允许不同的任务同时执行。当一个线程执行一个任务时,另外的线程可以执行另一个完全不同的任务。和Unity的渲染过程相关的线程有三种:主线程,渲染线程和工人线程(worker threads)。了解哪个线程负责哪些任务是很有用处的:一旦我们知道了在哪个线程上的任务执行的最慢,那么我们就应该集中努力优化在那个线程上的操作。

技术分享

我们可以缩放时间线视图,来更仔细的查看单独的任务。被调用的函数会显示在调用他的函数的下面。在这个例子中,我们已经放大了去看组成Shadows.RenderJob任务的其他独立任务。我们看到Shadows.RenderJob调用的函数发生在主线程,也可以看到工人线程执行和阴影相关的任务。主线程的一个任务WaitingForJob指示出主线程正在等待工人线程完成任务。从这里,我们可以推断出和阴影相关的渲染操作,在主线程和工人线程,花费了太多的时间。现在和问题相关的信息我们已经了解很多了。

其他的Profiler

当追踪帧率相关的性能问题时,尽管CPU usage profiler是最常用的Profiler,但是其他的Profiler也是十分有用的。熟悉他们提供的信息是很好的主意。

请尝试按照上面的步骤使用其他的Profiler,使用不同的视图,学习他们每帧提供的信息。例如,尝试使用渲染Profiler查看渲染数据是怎么逐帧变化的。

确定性能问题的原因

现在我们已经熟悉怎么在Profiler窗口中读取和分析性能数据了,我们可以开始查找引起性能问题的原因了。

排除垂直同步的影响

垂直同步(VSync)用来同步游戏的帧率和屏幕的刷新率。垂直同步会影响游戏的帧率,在Profiler窗口中可以看到影响。如果我们不是特别确定问题所在,垂直同步的影响可能看起来像性能问题,所以,在继续查找问题之前,应该学习怎么排除垂直同步的影响。

在CPU usage profiler中隐藏垂直同步信息

我们可以选择要在CPU usage profiler中隐藏的信息,这允许我们和忽略当前问题无关的信息。

按如下步骤在CPU usage profiler中隐藏垂直同步信息:

-选中CPU usage profiler。

-在CPU usage profiler窗口中左侧点击黄色的方框,标记着垂直同步,就可以隐藏信息。

不理会层级视图中的垂直同步信息

在CPU usage profile的层级视图中无法隐藏垂直同步信息,但是我们可以学习他表现出什么样子,这样我们就能不理会他。

当我们在层级视图中看见一个名为WaitForTargetFPS的函数时,这意味着我们的游戏在等待着垂直同步,我们无须调查这个函数,并安全的忽略他。

关闭垂直同步

垂直同步不是在所有的平台都可以关闭,许多平台(例如iOS)是强制开启的。当我们在不强制开启垂直同步的平开上开发时,当我们分析性能时,可以为整个项目关闭垂直同步。通过菜单Edit **> **Project Settings >Quality打开质量设置,在VSync Count的下拉菜单中选择Don’t Sync。

技术分享

分析渲染

渲染是常见的引起性能问题的原因。当我们尝试修复一个渲染性能问题之前,很重要的是确认我们的游戏是CPU受限还是GPU受限,因为他们需要用不同的方法去解决。

简单的说,CPU负责决定什么东西需要被渲染,而GPU负责渲染它。当渲染性能问题是因为CPU花费了太长时间去渲染一帧时,是CPU受限。当渲染性能问题是因为GPU花费了太长时间去渲染一帧时,是GPU受限。

识别游戏是否是GPU受限

识别是否是GPU受限的最快的方法是使用GPU usage profiler。不幸的是,并不是所有的设备和驱动都支持这个Profiler。在判断是否是GPU受限前,我们需要先检查GPU usage profiler在我们游戏的目标设备上是否可用。

检查GPU usage profiler是否可用,遵循下面的步骤:

-在Profiler窗口的左上角点击Add profiler。

-在下拉菜单中选择GPU。

如果目标设备不支持,我们可以看到右侧显示信息“不支持GPU分析”。

技术分享

如果没有看到这个信息,意味着GPU usage profiler支持我们的目标设备。在我们的例子中,如果GPU usage profiler可用,则可以按照如下步骤快速简单的确定游戏是否是GPU受限。

-选中GPU usage profiler。

-查看区域下方中间部分的cpu时间和gpu时间,这里显示的是当前选择的帧的信息。

如果GPU时间大于CPU时间,我们可以确定在游戏运行的这一帧中是GPU受限。

技术分享

如果GPU usage profiler不可用,我们仍然有办法确认游戏是否是GPU受限。我们可以通过CPU占用去确认。如果我们看到CPU在等待GPU完成任务,那么意味着GPU受限。我们可以通过以下步骤:

-选择CPU usage profiler。

-在窗口下部查看选择帧的详细信息。

-选择层级视图

-选择按Time ms列排序

如果函数Gfx.WaitForPresent在CPU usage profiler中消耗的时间最长,这表明cpu在等待gpu。也就是说GPU受限。

技术分享

解决GPU受限时的渲染问题

如果我们已经确定了是GPU受限,那么请阅读this article学习解决方案。

识别游戏是否是CPU受限

如果到这里还没有识别出游戏性能问题的原因,让我们现在调查cpu相关的渲染问题。

-选择CPU usage profiler。

-在Profiler窗口的上部,检查代表渲染的颜色的数据。我们可以点击颜色方块隐藏或显示不同种类的信息。

在慢的帧中,如果一大部分时间都花费在渲染上,那么表示渲染引起了问题。我们可以按照下面的步骤进一步挖掘性能信息:

-选择CPU usage profiler。

-检查窗口下方选中帧的详细信息。

-选择层级视图。

-点击Time ms列,把函数按消耗时间排序。

-点击列表中最上方的函数。

如果选中的函数是一个渲染函数,那么CPU profiler会高亮渲染部分。如果是这个原因,那么意味着是渲染相关的操作引起了游戏的性能表现,并且在这一帧是CPU受限的。注意函数名和函数是在哪个线程执行的,这些信息当我们尝试修复问题时十分有用。

解决CPU受限时的渲染问题

如果我们已经确定了是CPU受限,那么请阅读this article学习解决方案。

垃圾回收性能分析

接下来,我们检查是否是垃圾回收引起的性能瓶颈。垃圾回收是和unity自动内存管理相关的功能,这可能是一个慢的操作。

-点击选择CPU usage profiler

-在Profiler窗口,CPU usage profiler左侧,可以点击有颜色的下方快,控制显示或者隐藏相关的信息,也可以拖动他,按照自己的意愿排序。在下面的截图中,我们隐藏了垃圾回收器意外的所有其他信息,并且把垃圾回收器拖动到了最上方。

技术分享

如果在一个慢的帧中,一大部分时间被垃圾回收消耗,这指示着我们可能存在着垃圾回收过多的问题。我们可以更加深入的分析数据来确认。

-选中CPU usage profiler,查看下方窗口显示的关于当前选中帧的详细信息。

-选择层级视图

-选择按照Time ms排序

如果函数GC.Collect()出现在最上方,并且花费了过多的cpu时间,那么我们可以确认垃圾回收是我们游戏的问题所在。

解决垃圾回收问题

如果我们确定了游戏存在垃圾回收的问题,请阅读this article学习解决方案。

物理性能分析

到此为止,如果我们排除了渲染问题和垃圾回收问题,那么让我们查看复杂的物理运算是不是问题所在。

-点击选择CPU Usage profiler

-在Profiler窗口,CPU usage profiler左侧,可以点击有颜色的下方快,控制显示或者隐藏相关的信息,我们关注物理信息(橙色)

如果在一个慢的帧中,一大部分时间被物理运算消耗,这指示着物理运算可能引起了性能问题。我们可以更加深入的分析数据来确认。

-选中CPU usage profiler,查看下方窗口显示的关于当前选中帧的详细信息。

-选择层级视图

-选择按照Time ms排序

-点击信息列表的最上方,并选中

如果是物理函数,那么在Profiler上方会高亮物理运算的部分。如果是这种情况,说明游戏的性能问题是和物理运算相关的。

解决物理问题

如果确定了性能问题是物理运算引起的,那么下面几个资源将有助于解决问题:

-This page of the Unity Manual,虽然是写给iOS开发者的,但是其中一些物理优化的提示是对所有Unity游戏都适用的。

-This tutorial on optimising physics in a Unity game,包含一些有帮助的提示。

-Unite 2012 talk on optimization中讲物理的一节,包含了一些常见的物理问题的有用的总结。

脚本运行慢的问题

现在让我们检查,慢或者过于复杂的脚本是否是引起性能问题的原因。脚本,在这里,指的是非Unity引擎的代码。这通常意味着我们自己写的脚本,或者是一些我们在项目中使用的插件的代码。

-点击选择CPU Usage profiler

-在Profiler窗口,CPU usage profiler左侧,可以点击有颜色的下方快,控制显示或者隐藏相关的信息,我们关注脚本信息

如果在一个慢的帧中,一大部分时间被脚本运行消耗,这指示着这些慢的用户脚本可能引起了性能问题。我们可以更加深入的分析数据来确认。

-选中CPU usage profiler,查看下方窗口显示的关于当前选中帧的详细信息。

-选择层级视图

-选择按照Time ms排序

-点击信息列表的最上方,并选中

如果是用户脚本的函数,那么在Profiler上方会高亮脚本的部分。如果是这种情况,说明游戏的性能问题是和用户脚本相关的。

请注意,这里有一个意外情况:如果我们游戏包含和渲染相关的代码,例如屏幕后期效果脚本或者OnWillRenderObject 或者 OnPreCull函数中的代码,Profiler上方可能会高亮渲染数据,而不是脚本数据。

尽管这可能会最初会造成一点困惑,但是在层级视图和时间线视图中,通常我们可以追踪到负责任的代码。

解决脚本慢的问题

如果我们确认了是脚本引起的性能问题,这里有一些可以改进性能的技巧,推荐几个脚本优化的资源如下:

-This page in the Unity Manual 聚焦移动平台的脚本优化,但是其中的建议对所有的开发者都是有用的。

-This page in the Unity Manual 包含了一些如何在用户脚本中避免昂贵的函数调用的建议。

-Unite 2012 talk on optimization 包含一些常见脚本问题的有用的总结。

其他引起性能问题的原因

尽管我们已经讨论了四种最常见的引起性能问题的原因,我们的游戏仍然有可能遇到和这些方面不相关的问题。如果是这种情况,我们应该遵循上面的方法解决问题:收集数据,使用CPU usage profiler调查信息,找到引起问题的函数。一旦我们知道了引起问题函数的名字,我们可以在Unity Manual, Unity Forums or Unity Answers中查找函数的相关信息,这也许会节省您花费的时间。

扩展阅读

Unity Manual: Execution order

Unite 2012: Performance Optimization Tips and Tricks for Unity

Unite Europe 2016: Optimizing Mobile Applications

Unity性能优化(2)-官方文档简译