Unity3D性能优化——工具篇¶
性能优化是游戏项目开发中一个重要且必须的元素。用户和项目的需求在并且会持续增长。而即便在硬件设备高速发展的今天,游戏特效、画质、场景复杂度的需求也都向着榨干硬件性能的趋势提升,无论研发团队有多么丰富的经验积累,性能优化永远是一个非常棘手而又无法绕开的问题。
实际上,通过百度、谷歌、知乎可以搜到大把关于Unity性能优化的文章,但大多只是简单的论述、介绍、翻译和转载,或针对某中特定问题优化方式的教程。因此,我们在这里推出Unity3D性能优化系列文章,旨在给读者提供一个全面、易懂、可操作的unity优化教程,以便初学者学习使用。
在这里,我们的第一篇文章,主要作为一个引导,通过讲解对unity优化工具的了解及使用,给读者提供在实际项目中,进行游戏性能优化的流程和思路。
- 对于Unity性能优化,官方有非常好的教程,(参见官方教程)。如果英文水平一般,可以参考官方教程翻译。
- 同时你也可以去看看腾讯是如何做Unity手游性能优化的。
- 以及通过第三方优化平台的游戏性能分析报告来了解游戏开发中性能优化的主要方式及方向。
游戏性能简述¶
提起游戏性能,首先要提到的就是,不仅开发人员,所有游戏玩家都应该会接触到的一个名词:帧率(Frame rate)。
帧率是衡量游戏性能的基本指标。在游戏中,“一帧”便是是绘制到屏幕上的一个静止画面。绘制一帧到屏幕上也叫做渲染一帧。每秒的帧数(fps)或者说帧率表示GPU处理时每秒钟能够更新的次数。高的帧率可以得到更流畅、更逼真的动画。
现阶段大多数游戏的理想帧率是60FPS,其带来的交互感和真实感会更加强烈。通常来说,帧率在30FPS以上都是可以接受的,特别是对于不需要快速反应互动的游戏,例如休闲、解密、冒险类游戏等。有些项目有特殊的需求,比如VR游戏,至少需要90FPS。当帧率降低到30FPS以下时,玩家通常会有不好的体验。
而现阶段随着支持144HZ刷新率的硬件设备的涌现,能否维持对应高帧率又是一项新的指标,尤其是在电竞领域
但在游戏中重要的不仅仅帧率的速度,帧率同时也必须非常稳定。玩家通常对帧率的变化比较敏感,不稳定的帧率通常会比低一些但是很稳定的帧率表现更差。
虽然**帧率**是一个我们谈论游戏性能的基本标准,但是当我们提升游戏性能时,更因该想到的是渲染一帧需要多少毫秒。帧率的相对改变在不同范围会有不同的变化。比如,从60到50FPS呈现出的是额外3.3毫秒的运行时间,但是从30到20FPS呈现出的是额外的16.6毫秒的运行时间。在这里,同样降低了10FPS,但是渲染一帧上时间的差别是很显著的。
我们还需要了解渲染一帧需要多少毫秒才能满足当前帧率。通过公式 1000/(想要达到的帧率)。通过这个公式可以得到,30FPS必须在33.3毫秒之内渲染完一帧,60FPS必须在16.6毫秒内渲染完一帧。
渲染一帧,Unity需要执行很多任务。比如,Unity需要更新游戏的状态。有一些任务在每一帧都需要执行,包括执行脚本,运行光照计算等。除此之外,有许多操作是在一帧执行多次的,例如物理运算。当所有这些任务都执行的足够快时,我们的游戏才会有稳定且理想的帧率。当这些任务执行不满足需求时,渲染一帧将花费更多的时间,并且帧率会因此下降。
知道哪些任务花费了过多的时间,是游戏性能问题的关键。一旦我们知道了哪些任务降低了帧率,便可以尝试优化游戏的这一部分。这就是为什么性能分析工具是游戏优化的重点之一。
Unity3d性能分析工具¶
工欲善其事必先利其器,这里我们来讲解Unity3D优化所需的工具
如果游戏存在性能问题,游戏运行就会出现缓慢、卡顿、掉帧甚至直接闪退等现象。在我们尝试解决问题前,需要先知道其问题的起因,而尝试不同的解决方案。若仅靠猜测或者依据自身原有的经验去解决问题,那我们可能会做无用功,甚至引申出更复杂的问题。
在这些时候我们就需要用到性能分析工具,性能分析工具主要测试游戏运行时各个方面性能,如CPU、GPU、内存等。通过性能分析工具,我们能够透过游戏运行的外在表现,获取内在信息,而这些信息便是我们锁定引起性能问题的关键所在。
在我们进行Unity性能优化的过程中,最主要用的到性能分析工具包括,Unity自带的**Unity Profile**,IOS端的**XCode** ,以及一些第三方插件,如腾讯推出的UPA性能分析工具。
我们主要针对**Unity Profile**进行讲解,之后也会略微介绍另外一些性能分析工具。
Unity Profile¶
**Unity Profile**是Unity中最常用的官方性能分析工具,在使用Unity开发游戏的过程中,借助Profiler来分析CPU、GPU及内存使用状况是至关重要的。
首先我们来了解Unity Profile的面板: 我们通过Window——>Profiler来激活Unity Profile面板
在下图中我们可以看到Unity Profile面板,其中有很多profilers,每个profiler显示我们当前项目一个方面的信息,如CPU、GPU、渲染(Rendering)、内存(Memory)、声音(Audio)、视屏(Video)、物理(Physics)、ui及全局光照(global illumination)。
当项目运行时,每个profilers会随着运行时间来显示数据,有些性能问题是持续性的,有些仅在某一帧中出现,还有些性能问题可能会随时间推移而逐渐显出出来。
在面板的下半部分显示了我们选中的profilers当前帧的详细内容,我们可以通过选择列标题,通过这一列的信息值来排序。 在CPU usage profiler中的列表题分别为: Total:当前任务的时间消耗占当前帧cpu消耗的时间比例。 Self:任务自身时间消耗占当前帧cpu消耗的时间比例。 Calls:当前任务在当前帧内被调用的次数。 GC Alloc:当前任务在当前帧内进行过内存回收和分配的次数。 Time ms:当前任务在当前帧内的耗时总时间。 Self ms:当前任务自身(不包含内部的子任务)时间消耗。
当我们在层级视图中点击函数名字时,CPU usage profiler将在Profiler窗口上部的图形视图中高亮显示这个函数的信息。比如我们选中Cameta.Render,Rendering的信息就会被高亮显示出来。
我们可以Profiler的左下的下拉菜单中选择Timeline。
Timeline显示了两件事:cpu任务的执行顺序和哪个线程负责什么任务。线程允许不同的任务同时执行。当一个线程执行一个任务时,另外的线程可以执行另一个完全不同的任务。和Unity的渲染过程相关的线程有三种:主线程,渲染线程和worker threads。了解哪个线程负责哪些任务的用处非常之大,一旦我们知道了在哪个线程上的任务执行的速率最低,那么我们就应该集中优化在那个线程上的操作。
以上所显示的数据依赖于我们当前选择的profiler。例如,当选中内存时,这个区域显示例如游戏资源使用的内存和总内存占用等。如果选中渲染profiler,这里会显示被渲染的对象数量或者渲染操作执行次数等数据。
这些profiler会提供很多详细信息,但是我们并不总需要使用所有的profiler。实际上,我们在分析游戏性能时通常只是观察一个或者两个profiler,而不需要观察的我们可以通过右上角的”X”关闭,如果需要在添加回来,可以通过左上角Add Profiler。 例如,当我们的游戏运行的比较慢时,我们可能一开始先查看CPU usage profiler,CPU usage profiler也是在我们进行优化分析时最常用的Profiler。
当然,除了CPU usage profiler,Unity Profiler中其他的Profiler在一些场合也非常的有用,比如GPU、内存、渲染等,其使用方法和CPU usage profiler也是大同小异,可以按照以上的步骤来查看并学习。
我们在观察数据时,需要观察的目标有如下几点:
CPU: 1. GC Allow: 任何一次性内存分配大于2KB的选项。 每帧都具有20B以上内存分配的选项 。
GC相关的问题和优化,在之后我们会详细的介绍。
- Time ms:
注意占用5ms以上的选项
内存 1. Texture: 检查是否有重复资源和超大内存是否需要压缩等.。 2. AnimationClip: 重点检查是否有重复资源.。 3. Mesh: 重点检查是否有重复资源。
实际项目中的优化建议¶
在了解了Unity Profiler后,现在我们在一个实际项目中来进行一次性能分析。同时来了解一般在实际项目中,主要会引起也是我们主要去观察的性能问题出现在什么地方。
以下是我做的一个简单的游戏项目,并未做任何性能优化并且有大量引起性能问题的代码,可以更方便大家观察其性能问题,在之后我会把工程上传到github供初学者下载分析。
我们来看一下在CPU usage profiler面板中的可观察项,在项目中我们可以先关闭**VSync垂直同步**来提高帧率。
下图中我关闭了除VSync之外的显示,可以看到VSync的消耗
具体步骤是edit->project settings->Quality,在Inspector面板中,V Sync count选择don’t Sync.
我们来简单的介绍一下什么是垂直同步,以及关闭它之后会发生什么。
要理解垂直同步,首先明白显示器的工作原理。
显示器上的所有图像都是单个像素组成了水平扫描线,水平扫描线在垂直方向的堆积形成了完整的画面,无论是隔行扫描还是逐行扫描,显示器都有两种同步参数——水平同步和垂直同步。 垂直和水平是CRT显示器中两个基本的同步信号,水平同步信号决定了CRT画出一条横越屏幕线的时间,垂直同步信号决定了CRT从屏幕顶部画到底部,再返回原始位置的时间,而垂直同步代表着CRT显示器的刷新率水准。 在游戏项目中,如果我们选择等待垂直同步信号也就是打开垂直同步,在游戏中或许性能较强的显卡会迅速的绘制完一屏的图像,但是没有垂直同步信号的到达,显卡无法绘制下一屏,只有等85单位的信号到达,才可以绘制。这样FPS自然要受到刷新率运行值的制约。 而如果我们选择不等待垂直同步信号,那么游戏中绘制完一屏画面,显卡和显示器无需等待垂直同步信号就可以开始下一屏图像的绘制,自然可以完全发挥显卡的实力。 但是,正是因为垂直同步的存在,才能使得游戏进程和显示器刷新率同步,使得画面更加平滑和稳定。取消了垂直同步信号,固然可以换来更快的速度,但是在图像的连续性上势必会打折扣。 需要注意,LCD显示器其实也是存在刷新率的,但其机制与CRT不同,这里不做过多赘述,但是垂直同步和水平同步对于LCD显示器来说,一样是有必要的。
在关闭垂直同步后,我们继续看我们的项目
可以看到,我们以Total和Time ms排序,在图中拉黑的项(Camera Render)始终排在最前面。 Camera Render是相机渲染工作的CPU占用量,在实际项目中,渲染是最常见的引起性能问题的原因。 而因为渲染而引起的性能问题的优化是一个非常大的工程,这方面的优化方法在我们**后续的文章中**会有详细的教程去学习和分析。在这里,我们只需要先了解。 我们这个项目的优化中,无疑,渲染造成的性能损耗是一个大头。
如果说,在我们性能分析中,渲染已经没有什么问题,那么我们接下来要重点观察的就是GC,也就是**垃圾回收性能分析**。 我们按照GC Alloc的顺序来显示,可以看到下图。
在之前我们提到过,GC Alloc中,任何一次性内存分配大于2KB的选项,每帧都具有20B以上内存分配的选项 ,是需要我们重点关注的,显而易见,我们的项目中,对于GC的优化,也有很大的问题。关于CG的问题,我们会在下一篇Unity3D性能优化——CPU篇中,详细的介绍。
这里我们大致介绍一下GC的机制,要想了解垃圾回收如何工作以及何时被触发,我们首先需要了解unity的内存管理机制。Unity主要采用自动内存管理的机制,开发时在代码中不需要详细地告诉unity如何进行内存管理,unity内部自身会进行内存管理。 Unity内部有两个内存管理池,堆内存和栈内存,垃圾回收主要是指堆上的内存分配和回收,unity中会定时对堆内存进行GC操作。 当堆内存上一个变量不再处于激活状态的时候,其所占用的内存并不会立刻被回收,不再使用的内存只会在GC的时候才会被回收。 每次运行GC的时候,GC会检查堆内存上的每个存储变量,对每个变量会检测其引用是否处于激活状态,如果变量的引用不再处于激活状态,则会被标记为可回收,被标记的变量会被移除,其所占有的内存会被回收到堆内存上。 GC操作是一个极其耗费的操作,堆内存上的变量或者引用越多则其运行的操作会更多,耗费的时间越长。
如果我们也排除了GC的问题, 那么再接下来,我们就要考虑到是否是脚本的一些问题造成性能损耗。
这里的脚本,可能是我们自己写的代码,也有可能是我们使用的一些插件的代码。在CPU usage profiler面板中,我们可以关注Script这一项。
如果在一个很慢的帧中,一大部分时间被脚本运行所消耗,这意味着这些慢的脚本可能就是引起性能问题的主因。我们可以更加深入的分析数据来确认。
首先我们按照Time ms来排序,然后选择信息列表中的项目,如果是用户脚本的函数,那么在Profiler上方会有高亮脚本的部分。这种情况,说明游戏的性能问题是和用户脚本相关的,如下图中的显示,这部分脚本性能问题一定是与我们FixedUpdate有关。
同时,我们还可以再关注一些物理、ui方面的性能问题。
在上面我们讨论的,是几种最常见的性能问题,在实际项目优化中,如果有性能问题也逃不开这些,如果在这些方向都已经达到了我们的要求,但我们的游戏仍然有性能问题,我们应该遵循上面的方法解决问题:收集数据——>使用CPU usage profiler查看信息——>找到引起问题的函数。一旦我们知道了引起问题函数的名字,我们便可以针对性的,对其进行优化处理。
其他性能分析工具¶
在开头我们说过,在我们进行Unity性能优化的过程中,最主要用的到性能分析工具包括,Unity自带的**Unity Profile**,IOS端**XCode Capture GPU frame**以及一些第三方插件,如腾讯推出的UPA性能分析工具。
这里我们简单的介绍一下XCode和UPA.
**Xcode**是 Mac OS X上的集成开发工具。在我们打包Unity IOS的项目时,必须使用到Xcode来帮助我们打包及发布。
Xcode的功能也十分的强大,在我们开发IOS端时,可以使用其GPU frame Capture 功能为我们的项目进行性能优化分析。
在unity中,我们打包时在Run In Xcode as 选择debug模式,并且勾选Development Build
打包完成后,使用Xcode打开文件,在Xcode中选择Product ——> Scheme——> Manage Schemes
然后会出现如下界面
我们双击这个项目会出现如下界面
然后我们在左侧选中Run,然后在右侧面板选择Options
在GPU frame Capture中选择OpenGL ES或者Metal。 在Debug模式下运行项目,当项目在真机上完全加载后,就可以进入Debug Navigator(View ——> Navigators ——> Show Debug Navigator)
以下是GPU frame Capture具体功能的界面,在图形化界面中,可以在游戏运行时清晰的了解到CPU、GPU、内存的使用情况。
XCode的Capture GPU frame功能能**高效且定量**地定位到GPU中shader的消耗。
参考¶
摘自https://zhuanlan.zhihu.com/p/39529241