Unity性能优化系列——加载与资源管理
uwa4d
2021-04-20 23:45
四年前,我们一一讲解了Unity主流模块的性能优化知识点,俗称“小白版”。随着这几年引擎本身、硬件设备、生产标准等的升级,UWA也在不断更新自己的优化规则和方法,源源不断地输出给开发者。作为性能优化手册的“升级版”,【Unity性能优化系列】将尽量用通俗易懂的表达方式造福于更多的开发者。本期分享资源管理相关的知识点。
打开网易新闻查看精彩图片
经常有团队问:为什么我的游戏加载这么慢,能不能像***游戏一样秒切场景?游戏火爆的原因是什么?为了不让辛苦的游戏卡在真机上或者秒暖手宝,我们必须充分利用UWA报告中的加载模块和资源管理模块,我们将在后面逐步分析未来。
1.加载模块注意事项
这里先给大家普及一些loading相关的attention指标。下图是Overview报告中加载模块的tab。我们可以看到左侧有几个重要的参数指标:
打开网易新闻查看精彩图片
1.加载.更新预加载
这是Unity引擎的主要加载功能。这一项一般在切换场景或者异步加载资源时开销较大。一般来说,加载的资源越多、越复杂,Load.UpdatePreloading 的耗时就越多。
打开网易新闻查看精彩图片
在优化该功能之前,建议定位其耗时瓶颈。通过上报的CPU调用栈,可以查看函数在运行过程中详细的堆栈趋势,对函数的耗时分配情况一目了然,从而有针对性地进行优化。
打开网易新闻查看精彩图片
2. 资源。卸载未使用的资产
这个功能是指卸载不用的资源。开销主要取决于场景中资产和对象的数量。数量越多,就越费时。在优化性能的时候,除了耗时峰值,我们还需要关注这个函数的调用次数。
打开网易新闻查看精彩图片
一般情况下,场景切换过程中引擎会自动调用一次,UWA建议每隔10-15分钟手动调用一次。
同时,研发团队可以尝试在游戏运行时通过Resources.UnloadAsset移除一个已经确定不再使用的资源。这个API对于移除单个资源是非常高效的,同时也可以减轻统一处理时Resources.UnloadUnusedAssets的压力。
下图是报表中加载相关函数的栈信息。在堆栈中,GarbageCollectAssetsProfile 是由调用 Resources.UnloadAssetsUnused 引起的。如果此项占用过多,需要注意是否主动调用Resrouces.UnloadUnusedAssets过于频繁。
打开网易新闻查看精彩图片
3.气相色谱。搜集
GC调用的频率主要受堆内存的影响。函数分配堆内存的频率越高,GC 就会来得越快。因此,当我们的GC.Collect函数被频繁调用时(如下图所示),尤其是随着游戏运行时间的增加,越来越频繁,我们需要注意是否有函数分配高且频繁的堆内存后操作,这部分可以使用GOT Online的Mono模式,检查是否有Mono分配过快或过高的现象。
打开网易新闻查看精彩图片
4.实例化
打开网易新闻查看精彩图片
这里统计的是资源实例化的耗时。项目的资源越复杂,实例化的次数越多,卡顿的感觉就越明显,但这部分往往容易被大家忽略。UWA是如何处理好这部分的呢?问题?下面我们将结合UWA真机测试报告中的【资源管理】模块进行具体说明。
2.资源管理
这里的资源管理指的是资源调用频率和耗时等策略,因为影响加载体验的无外乎两个角度:加载频率和每次加载耗时。在真机测试报告中,我们可以在【资源管理】选项卡后看到如下检测项:
打开网易新闻查看精彩图片
功能这么多,我们应该注意哪些细节呢?说以下核心点:
1.关注加载耗时
无论是AssetBundle还是资源加载,都需要重点关注耗时的。这里我们打开一个资源加载标签,下面可以看到整个运行过程中的资源调用详情,最后一栏是耗时。
打开网易新闻查看精彩图片
在资源特定信息中,检查资源以查看其在运行期间的调用详细信息。对应上面的截图,我们可以进一步查看加载AssetBundle是否耗费了这么多时间。
打开网易新闻查看精彩图片
2、集中于短时间内的密集通话
无论是AssetBundle还是资源加载,都要注意加载的频率。通常,对于频繁加载的对象,我们可以创建一个缓存池,加载一次就添加到缓存中,以后就不需要再加载了。
如下图,这些频繁加载的AssetBundle可能每次有5ms或者50ms的耗时游戏评测,后面可以直接0。
打开网易新闻查看精彩图片
这里同样需要注意的是,同一个资源在一帧内被多次加载的问题。
如下图,在这一帧调用了5次,是错误的。
打开网易新闻查看精彩图片
3.注意资源缺失
打开网易新闻查看精彩图片
在资源加载列表中,有些项目会出现[不存在]的资源3D道具,说明这些资源都是因为不在指定路径下而加载失败的资源。一般情况下,此类资源是由于版本迭代、删除/迁移、对应代码没有修改/注释造成的。
加载这些[不存在的]资源只会造成少量的CPU开销,但更重要的是,对这些[不存在的]资源进行故障排除可以避免导致崩溃和卡顿的逻辑问题。
4.频繁实例化/销毁
具有大量操作或高时间消耗的资源。频繁的Instantiate会造成一定量的堆内存分配,从而加快系统调用GC的频率。更重要的是,频繁的实例化会造成一定的CPU耗时峰值,从而影响游戏的流畅度,所以这部分也是我们需要注意的。
打开网易新闻查看精彩图片
对于这种频繁实例化的资源,使用缓存池来复用实例化次数过多的GameObject,从而减少GameObject实例化的耗时。
5.激活和停用
这种排查方法和实例化类似,重点看调用频率和耗时。
打开网易新闻查看精彩图片
比较Activate和Deactivate的调用次数,因为如果两者相差太大,说明存在无用的Activate/Deactivate操作。
打开网易新闻查看精彩图片
比如某个资源的Activate操作数很高(下图中的Gold_2和Gold_4),为什么会这么高?有必要吗?我们可以复制资源名称,在Deactivate资源列表中搜索,看看是否真的需要那么多状态激活。
打开网易新闻查看精彩图片
停用 Gold_2
打开网易新闻查看精彩图片
为 Gold_4 停用
打开网易新闻查看精彩图片
这说明10000多次Deactivate操作的差异是没有意义的。
对于以上资源,我们可以在C#端创建一个特例缓存来记录这个对象的Active状态(True或False)。在调用SetActive之前,先判断当前状态是否已经是我们要切换到的状态,如果没有调用。这是因为SetActive的操作会从C#转到C++层,所以我们可以通过在C#中进行状态判断来减少这种跨语言的操作,从而避免不必要的耗时。
6.AssetBunde常驻优化
之所以关注这个参数,是因为它会影响项目运行过程中的内存占用。要知道有一部分Unity内存和AssetBundle驻留导致的Serializedfile有关。一般来说,我们建议控制的AssetBundle资源数量在1000个以下。考虑到这个指标与项目本身的复杂度有关,需要自己做一些实验来平衡CPU和内存的平衡。
打开网易新闻查看精彩图片
使用缓冲池可以优化资源的加载,AssetBundle的加载类似。频繁加载同一个AssetBundle通常是不合理的(如下图所示)。对于频繁加载和卸载的AssetBundle,建议将其添加到缓存中并留在内存中。
打开网易新闻查看精彩图片
3.Shader.Parse/CreateGPU程序
如果没有正确解析和加载Shader资源,也会造成较大的CPU开销。由于Shader的内存占用小,但是加载时间比较长,我们建议最好在项目开始运行的时候就加载所有的Shader资源,然后缓存起来。
1.着色器。解析
打开网易新闻查看精彩图片
这个函数的耗时主要是Shader的加载和解析,通常是Shader的反复加载。优化的时候要看具体的Shader加载情况。具体可以从以下三点入手:
(1)避免使用Standard,使用其他Shader代替Standard Shader。注意检查Standard Shader是否因模型导入而加载到AssetBundle中;
打开网易新闻查看精彩图片
(2)解决Shader冗余问题unity 异步加载场景,这部分可以结合Shader内存趋势来看,如下图。
打开网易新闻查看精彩图片
如果你的Shader资源没有缓存在内存中,当你切换出场景时Shader会被释放,切入场景时会加载Shader,造成大量的重复开销。解决这个问题,只需要将Shader剥离,通过dependencies做成一个单独的ABunity 异步加载场景,然后加载完缓存不用卸载,后面就不需要加载Shader了。
(3)减少Shader的Keyword。
研发团队可参考以下资料:
《一种Shader变体收集打包编译优化的思路》:
2.Shader.CreateGPU程序
打开网易新闻查看精彩图片
该API的CPU使用率是Shader第一次渲染时创建GPU程序的耗时,耗时与渲染Shader的复杂度有关。对此,建议研发团队通过ShaderVariantCollection加载Shader,加载后warmup,避免游戏运行时Shader生成耗时的Shader.CreateGPUProgram。
以上是加载优化时需要注意的一些问题。如何操作需要你结合项目的实际情况。同时,结合UWA的在线测评服务,可以快速帮您定位性能瓶颈。