Unity性能调优手册1:开始学习性能调优

翻译自https://github.com/CyberAgentGameEntertainment/UnityPerformanceTuningBible/

1开始学习性能调优

本章介绍调优前的准备工作和调优流程。首先,我们将介绍在开始性能调优之前需要决定和考虑的内容。如果您的项目仍处于早期阶段,请查看一下。即使你的项目已经比较先进了,最好还是再检查一下,看看你是否考虑到了本节中列出的信息。接下来,我们将解释如何解决出现性能下降的应用程序。通过学习如何隔离问题的原因以及如何解决问题,您将能够实现一系列性能调优流。

前期准备

在进行性能调优之前,确定您想要达到的指标。这说起来容易,但实际上是一项极具挑战性的任务。这是因为世界上充斥着各种规格的设备,不可能忽视低规格设备的用户。在这种情况下,有必要考虑各种因素,如游戏规格,目标用户群体,以及游戏是否会在海外开发。这项工作单靠工程师是无法完成的。与其他专业人员协商确定质量线是必要的,技术验证也是必要的。
当没有足够的功能实现或资产来度量负载时,从初始阶段确定这些指标是非常困难的。因此,一种方法是在项目进展到一定程度后确定它们。然而,重要的是要确保在项目进入批量生产阶段之前做出决定。这是因为一旦开始批量生产,改变的成本将是巨大的。决定本节介绍的指标需要时间,但不要着急,要坚定地进行
假设您有一个项目现在处于后期制作阶段,但是在低规格终端上出现了渲染瓶颈。内存使用已经接近极限,因此不能根据距离切换到低负载模型。因此,我们决定减少模型中的顶点数量。首先,我们将对数据重新排序进行约简。将需要一个新的采购订单。接下来,厂长需要再次检查质量。最后,我们还需要调试。这是一个简化的描述,但实际上会有更详细的操作和调度。批量生产后,将有数十到数百个资产需要如上所述进行处理。这既耗时又费力,对项目来说可能是致命的。为了防止这种情况的发生,创建最繁重的场景并提前验证其是否符合指标是非常重要的。

确定指标

确定指标将帮助你确定要实现的目标。另一方面,如果没有指标,项目将永远不会结束。
表1.1以下是您应该决定的指标列表
在这里插入图片描述

考虑到移动设备更换时间延长的背景,您可以使用大约四年前的中端产品作为目前的基准。
让我们考虑一个实际的例子。假设您有一个具有以下目标的项目:
•我们希望改进竞争对手应用程序中的所有错误。
•我们想让它运行流畅,尤其是在游戏中。
•除了以上几点,我们希望和竞争对手一样优秀。
当团队用语言描述这些模糊的目标时,生成了以下指标。
•帧率-从电池消耗的角度来看,游戏内60帧和游戏外30帧。
•内存-为了加快过渡时间,设计应该在游戏中保留一些游戏外的资源。使用的最大内存量应为1gb。
•过渡时间-游戏内外的过渡时间应该与竞品相同。在时间上,应该在3秒内。
•温度-与竞品水平相同。在验证过的设备上连续1小时不发热。(未充电)
•电池-与竞品处于同一水平。在测试设备上连续使用1小时后,电池消耗约为20%

根据游戏类型设置帧率

在这种情况下,游戏的主题是流畅运行,所以帧率设置为每秒60帧。高帧率也适用于节奏动作游戏和第一人称射击游戏(FPS)等具有严格判断的游戏。然而,高帧率有一个缺点。帧率越高,消耗的电池电量越多。此外,使用的内存越多,它在挂起时被操作系统杀死的可能性就越大。考虑到这些优势和劣势,为每种游戏类型确定一个合适的目标。

了解游戏最大内存使用量

本节主要关注最大内存使用量。要确定最大内存使用量,首先确定受支持的设备上有多少内存可用。基本上,用保证工作的最低规格设备来验证这一点是一个好主意。由于操作系统版本不同,内存分配机制可能会发生变化,因此建议尽可能多准备不同主版本的设备。
另外,由于测量逻辑因测量工具的不同而不同,请确保只使用一种工具。以下是作者在iOS上进行验证的描述,供参考。在验证项目中,在运行时生成Texture2D,并测量崩溃所需的时间。代码如下。通过频繁的创建Texture2D,分配字节,人为的产生崩溃

private List<Texture2D> _textureList = new List<Texture2D>();
...
public void CreateTexture(int size) {
    
    
Texture2D texture = new Texture2D(size, size, TextureFormat.RGBA32, false);
_textureList.Add(texture);
}

测试出内存崩溃阈值如下
在这里插入图片描述

译者增加部分
Unity中监听低内存回调Application.lowMemory

float m_lastTime = Time.time;
Application.lowMemory += this.OnLowMemory;

private void OnLowMemory()
{
    
    
    if (m_lastTime - Time.time > 10)
    {
    
    
        //释放无用资源
        Resources.UnloadUnusedAssets();
        GC.Collect();
        m_lastTime = Time.time
    }
}

但是注意Resources.UnloadUnusedAssets()不会立即生效,为了防止在低内存时频繁触发lowMemory回调从而频繁触发回收,可以在调用上次一定间隔后再调用释放资源
https://zhuanlan.zhihu.com/p/622635151

内存测量工具

我们建议使用原生兼容的工具,如Xcode和AndroidStudio进行内存测量。例如,Unity Profiler不测量插件分配的本机内存。在IL2CPP构建的情况下,IL2CPP元数据(大约100MB)也不包括在测量中。另一方面,在原生工具Xcode的情况下,应用程序分配的所有内存都是测量的。因此,最好使用与本机兼容的工具来更准确地度量值。
例如Xcode Debug Navigator
在这里插入图片描述

确定哪些设备可以保证工作

确定最小保证终端作为指标,以确定性能调优要走多远。按照soc来划分
推荐安兔兔跑分作为参考依据

确定质量设置规范

以下选项可分为高、中、低质量设置。
•屏幕分辨率
•显示的对象数量
•阴影
•后期效果功能
•帧速率
•能够跳过cpu密集型脚本等
译者增加部分
有几个核心问题
1.如何划分几档机的质量,根据cpu,gpu,内存
2.如何设置推荐配置
3.如何根据几档机,设置哪些质量参数

预防

与缺陷一样,随着时间的推移,性能下降可能有各种各样的原因,从而增加了调查的难度。最好在应用程序中实现一种机制,使您能够尽早注意到问题。一种简单而有效的方法是在屏幕上显示当前应用程序状态。建议屏幕上始终至少显示以下元素
1.当前帧率
2.当前内存使用
在这里插入图片描述

虽然帧率可以通过用户体验检测到性能下降,但内存只能通过崩溃检测到。通过在屏幕上不断显示内存泄漏,可以提高早期检测到内存泄漏的概率,如下表所示。这种显示方法可以进一步改进,使其更有效。例如,如果目标帧率是每秒30帧,那么在25到30帧之间显示绿色,在20到25帧之间显示黄色,低于30帧显示红色。这样,您就可以直观地一目了然地看到应用程序是否满足标准

进行性能调优

无论您如何努力在性能下降发生之前阻止它,都很难完全阻止它。这是不可避免的。性能退化是发展不可分割的一部分。总有一天,您将不得不面对性能调优。在下面的部分中,我们将解释应该如何处理性能调优

性能调优准备

在开始性能调优之前,让我们首先介绍一个重要的态度。例如,假设您有一个帧速率较慢的应用程序。显然,显示了几个丰富的模型。你周围的人都说一定是这些模型造成的。我们需要仔细研究证据,看看情况是否真的如此。在调优性能时要记住两件事。
首先是测量和确定原因。不要猜。
第二,修正后,一定要对比结果。您可能想要比较前后的概要文件。关键是要全面检查性能下降,而不仅仅是在修改区域。性能调优的可怕之处在于,在极少数情况下,修改后的部分会更快,但系统其他部分的负载会增加,整体性能会下降。到此为止了。
在这里插入图片描述

识别问题原因,确认系统运行速度变快。这是性能调优的重要态度。

性能下降的类型

性能下降可能指的是不同的事情。在本文中,我们对这三大类进行如下定义。崩溃,黑屏,长时间加载
在这里插入图片描述

首先,崩溃可以分为两种主要类型:“内存溢出”或“程序执行错误”。后者不属于性能调优的范围,因此本文将不涉及具体内容。

单独分析内存占用过多的原因

内存泄露

内存溢出的一个可能原因是内存泄漏。为了检查这一点,让我们看看内存使用是否随着场景转换而逐渐增加。这里的场景转换不只是屏幕转换,还包括大屏幕的变化。例如,从标题屏幕到游戏外部,从游戏外部到游戏内部等等。按照以下步骤测量内存使用情况。
1.注意某个场景中的内存使用情况
2.过渡到另一个场景
3.重复“1”至“2”约3至5次
如果测量结果显示内存使用量净增加,那么肯定有什么东西泄漏了。这可以称为隐形缺陷。首先,让我们消除泄漏。
在进行“2”过渡之前,夹几个屏幕过渡也是一个好主意。这是因为可能只有在特定屏幕上加载的资源才会异常泄露。
一旦你确定了泄漏,你就应该寻找泄漏的原因。

重复的原因

这是作者的经验,但在某些情况下,由于资源释放后的时间问题,一些资源没有被释放
UnloadUnusedAssets)。这些未释放的资源在过渡到下一个场景时被释放。相反,重复转换的内存使用量逐渐增加最终会导致崩溃。

只是内存占用高

如果只是内存占用高而没有泄漏,则有必要探索可以减少内存占用的领域。

使用工具深究内存泄露

首先,让我们重现内存泄漏,然后使用以下工具查找原因。

Profiler (Memory)

这是一个默认包含在Unity编辑器中的分析器工具。因此,您可以轻松地执行度量。基本上,您应该使用“详细”和“收集对象引用”设置和调查快照内存。与其他工具不同,该工具不允许对测量数据进行快照比较

Memory Profiler

这个必须从包管理器安装。它以树状图的形式图形化地显示内存内容。它由Unity正式支持,并且仍在频繁更新。

Heap Explorer

这必须从包管理器安装。它是一个由个人开发的工具,但它非常易于使用和轻量级。它是一个可以用来以列表格式跟踪引用的工具,这是对v0.4及更早版本的Memory Profiler的一个很好的补充。当v0.5 Memory Profiler不可用时,它是一个很好的替代工具。

减少内存

减少记忆的关键是从大的区域切掉。因为1000个1KB只会减少1MB。然而,如果你将10mb的纹理压缩到2mb,你可以将其减少8mb。考虑到成本效益,你应该从最大的项目开始并首先减少它们。

资源Assets

如果简单视图有很多资产,这可能是由于不必要的资产或内存泄漏。这里与assets相关的区域是图中矩形包围的区域
在这里插入图片描述

在这种情况下,有三件事需要调查

不必要的资源

不必要的资产是指当前场景根本不需要的资源。
例如,仅在标题屏幕中使用的背景音乐即使在我们的游戏中也会驻留在内存中。首先,确保只使用当前场景所必需的资源。
译者增加部分
游戏会分为launcher启动场景和所有可热更场景。launcher场景不会更新,随着apk发布,且不会卸载(因为GameMgr,管理器模块等都挂载在该场景上),要检查是否有不相干资源被挂载到launcher场景上(例如一张大图)

重复资源

这在支持资产包时经常发生。同一个资产被包含在多个资产包中,这是由于资产包依赖关系分离不好。但是,过多的依赖关系分离会导致下载文件数量的增加和文件部署成本的增加。在测量这个区域时,可能有必要培养一种平衡感。
译者增加部分
打包如何避免冗余,可参考YooAsset,如果assetA会打入bundleA,bundleB,把assetA分配一个按照它路径为名的bundleC
【腾讯文档】YooAsset零冗余构建
https://docs.qq.com/doc/DWmdrWWtzWFdHYmZu
如何划分打包颗粒

检查规则

检查每一个项目,看看是否遵守了规定。如果没有相关规定,请检查您是否没有正确估计内存。
例如,对于纹理,您可能需要检查以下内容
•尺寸合适吗?
•压缩设置是否合适?
•MipMap设置是否合适?
•读/写设置是否合适等

GC (Mono)

如果在Simple View中有很多GC (Mono),两个原因
1.在同个时间分配了大量的GC,例如new字节流数组
2.在很频繁(每一帧)的分配GC,例如Update中new对象
可以用对象池改进
译者增加部分
可以参考下GameFramework中对象池管理。不过会引入新的问题,减少了频繁new,但是对象池何时释放,释放频率是个需要处理的问题
【腾讯文档】GF对象池
https://docs.qq.com/doc/DWnlIRnpMWmhEeElt

其他

在详细视图中检查可疑项目。例如,您可能希望打开Other项一次以进行调查。
在这里插入图片描述

根据我的经验,SerializedFile和PersistentManager。Remapper非常臃肿。如果您可以跨多个项目比较值,那么最好将处理失败的原因隔离一次。比较每个值可能会发现异常值。

插件

到目前为止,我们已经使用Unity的测量工具来隔离问题的原因。
然而,Unity只能测量由Unity管理的内存。换句话说,没有测量插件分配的内存量。检查第三方产品是否分配了额外的内存。
使用本地测量工具(Xcode中的Instruments)。
译者增加部分
unity有很多好用的插件,并不是导入到工程中直接使用就可万事大吉。有时要对插件C#代码进行profile测量
【腾讯文档】 EasyTouch使用引用池
https://docs.qq.com/doc/DWk9MY2lyQ055SkVD
对于引入的unity插件,可以搜下网络上对他优化部分有无说明,然后在start,update中打入profile标签测量下

检查资源规格,同屏人数等

这是最后一步。如果到目前为止我们所涵盖的内容没有什么可以削减的,我们别无选择,只能考虑规格说明。下面是一些例子
•改变纹理的压缩比-为纹理的一部分增加压缩比一步
•改变加载/卸载的时间-在常驻内存中释放对象并每次加载它们。
•改变加载规格-减少一个角色在游戏中加载的数量。
所有这些改变都会产生巨大的影响,并可能从根本上影响游戏的乐趣。因此,规范考虑是最后的手段。确保尽早评估和测量内存,以防止这种情况发生。

隔离负载增加原因

在游戏运行中,瞬时和稳定负载增加如下所示
在这里插入图片描述

深究瞬时高负载

GC引起的尖峰

如果正在进行GC(垃圾收集),则GC.Alloc(new对象)应该减少。首先应该减少的是那些具有成本效益的领域。建议关注以下项目。
•每帧分配的区域
•大量分配发生的区域
但这并不意味着分配应该为零。例如,没有办法防止在Instantiate进程期间发生分配。在这种情况下,池化(每次使用对象而不是生成对象)是有效的。
译者增加部分
在Unity中,asset,bundle,GameObject可以采用三重池管理,可以参考GameFramework中部分
【腾讯文档】GF实体,对象池,资源管理,自动释放
https://docs.qq.com/doc/DWklIdGFNWlhtWE1K

由于处理繁重导致的尖峰

如果GC不是原因,则会立即执行某种繁重的处理。同样,让我们使用Deep Profile来调查哪些处理是繁重的,有多少处理是繁重的,并检查处理时间最长的区域。以下是一些最常见的临时繁重进程。
•实例化处理
•大量对象或深层层次对象的主动切换
•屏幕捕获处理等。
由于这是一个高度依赖于项目代码的部分,因此没有一刀切的解决方案。如果实际测量揭示了原因,请与项目成员分享测量结果,并讨论如何改进。
译者增加部分
实例化可以进行分帧处理,每次帧只允许实例化耗时多少ms

深究稳定高负载

在提高稳态处理负荷时,减少单帧内的处理是很重要的。在单帧内执行的处理可以大致分为CPU处理和GPU处理。首先,最好隔离这两个进程中的哪个是瓶颈,或者哪个具有相同的处理负载。
确定造成搞负载是在CPU限制还是GPU限制
作为分离两者的简单方法,如果以下任何一种情况适用于您,那么您很有可能受到gpu限制。
•当屏幕分辨率降低时,处理负载显着改善
•使用Profiler 测量时,存在Gfx.WaitForPresent
否则,如果这些不存在,则有可能出现CPU限制。

CPU限制

CPU 限制使用CPU (Profiler),调查使用Deep Profile 检测代码块。如果在持续改进后仍不能达到目标减值,您可能需要回到“确定质量设置规范”

GPU限制

使用Frame Debugger 进行调查

分辨率合适吗?

在GPU限制中,分辨率对GPU的处理负载影响较大。因此,如果分辨率设置不合适,首要任务应该是将其设置为合适的分辨率。
首先,检查分辨率是否适合假设的质量设置。检查的一个好方法是查看Frame Debugger中正在处理的渲染目标的分辨率。
•只有UI元素以设备的全分辨率呈现。
•用于后期效果的临时纹理具有高分辨率等。
译者增加部分
如何实现3d分辨率与UI分辨率分离

有不需要的东西吗?

检查Frame Debugger ,看看是否有任何不必要的drawing。
例如,可能有一个不需要的相机活动,正在绘制场景背后不相关的对象。
如果有很多情况下,由于其他障碍物而浪费了之前的绘图,使用遮挡剔除Occlusion Culling
可能是一个很好的选择。
tips
请注意,遮挡剔除需要提前准备数据,并且随着数据部署到内存中,内存使用将增加。通常的做法是在内存中构建预先准备好的信息,以这种方式提高性能。由于内存和性能通常是成反比的,所以在使用某些东西时也要注意内存是一个好主意。

合批是否合适?

批处理是一次绘制所有对象的过程。批处理是有效的降低GPU瓶颈限制,因为它提高了绘图效率。例如,静态批处理可用于组合多个固定对象的网格。
• Static Batching
• Dynamic Batching
• GPU Instancing
• SRP Batcher, etc
译者增加部分
合批是降低了CPU的瓶颈限制
【腾讯文档】静态、动态合批与GPUInstancing
https://docs.qq.com/doc/DWm1Ib25MZEFHQW9y

单独看一下负载

如果处理负载仍然很高,那么唯一的方法就是单独查看它。这可能是对象有太多的顶点或着色器处理导致的问题。要隔离这种情况,请切换每个对象的活动状态,并查看处理负载如何变化。具体来说,我们可以尝试停用背景,看看会发生什么,停用角色,看看会发生什么,等等。
一旦确定了具有高处理负荷的类别,应进一步检查以下因素。
•是否有太多的对象要画?
-考虑是否有可能一次画出所有的。
•每个对象的顶点数量是否太大?
-考虑减量和LOD
•用一个简单的着色器代替处理负载是否得到改善?
-回顾Shader的处理

其他

可以说,每一个GPU的处理都是堆积而重的。在这种情况下,唯一的方法就是一个接一个地稳步改进。
此外,与CPU限制一样,如果无法达到目标降低,则最好回到“确定质量设置规范”并重新考虑。

总结

在本章中,我们讨论了性能调优“之前”和“期间”需要注意的事项。
在性能调优之前和调优期间需要注意的事项如下
•确定“指标”、“保证设备”和“质量设置规范”。
-量产前的各项指标的验证和确定。
•创建一种容易发现性能下降的机制。
以下是在性能调优期间要记住的事情。
•找出导致性能下降的原因,并采取适当的措施。
•确保遵循“测量”、“改进”和“再测量(检查结果)”的顺序。
正如到目前为止所解释的,度量和隔离性能调优的原因非常重要。即使本文档中没有描述的情况发生,如果遵循基本原则,也不会成为主要问题。如果您以前从未做过性能调优,我们希望您将本章中的信息付诸实践。

猜你喜欢

转载自blog.csdn.net/luoyikun/article/details/133296148