《Unity 挪动端游戏性能优化简谱》从 Unity 挪动端游戏优化的一些根底探讨登程,例举和剖析了近几年基于 Unity 开发的挪动端游戏我的项目中最为常见的局部性能问题,并展现了如何应用 UWA 的性能检测工具确定和解决这些问题。内容包含了性能优化的根本逻辑、UWA 性能检测工具和常见性能问题,心愿能提供给 Unity 开发者更多高效的研发办法和实战经验。
明天向大家介绍文章第二局部:资源内存、Mono 堆内存等常见游戏内存管制,共 13 大节,蕴含了纹理资源、网格资源、动画资源、音频资源、材质资源等多个资源内存以及 Mono 堆内存等常见的游戏内存管制解说。
(全文长约 11400 字,预计浏览工夫约 20 分钟)
文章第一局部《Unity 挪动端游戏性能优化简谱之 前言》可戳此回顾,残缺内容可返回 UWA 学堂查看。
1. 总览
1.1 概念解释
首先,在探讨内存相干的各项参数和制订规范之前,咱们须要先理清在各种性能工具的统计数据中常呈现的各种内存参数的理论含意。
在安卓零碎中,咱们最常见到和关怀的 PSS(Proportional Set Size)内存,其含意为一个过程在 RAM 中理论应用的空间地址大小,即理论应用的物理内存。就后果而言,当一个游戏过程中 PSS 内存峰值越高、占以后硬件的总物理内存的比例越高,则该游戏过程被零碎杀死(闪退)的概率也就越高。
而在 PSS 内存中,除了 Unused 局部外,咱们个别比较关心 Reserved Total 内存和 Lua、Native 代码、插件等零碎缓存、第三方库的本身调配等内存。Reserved Total 占比个别较高,故其大小和走势,也是 UWA 性能剖析工具的次要统计对象(对于应用到 Lua 的我的项目,UWA 另外提供了 Lua 专项测试报告统计 Lua 内存,下文还会提到)。
Reserved Total 和 Used Total 为 Unity 引擎在内存方面的总体调配量和总体使用量。一般来说,引擎在分配内存时并不是向操作系统“即拿即用”,而是首先获取一定量的间断内存,而后供本人外部应用,待空余内存不够时,引擎才会向零碎再次申请一定量的间断内存进行应用。
留神:对于绝大多数平台而言,Reserved Total 内存 = Reserved Unity 内存 + GFX 内存 + FMOD 内存 + Mono 内存
(1)Reserved Unity 内存
Reserved Unity 和 Used Unity 为 Unity 引擎本身各个模块外部的内存调配,包含各个 Manager 的内存占用、序列化信息的内存占用和局部资源的内存占用等等。
通过针对大量我的项目的深度剖析,UWA 发现导致 Reserved Unity 内存调配较大的起因次要有以下几种:
序列化信息内存占用:Unity 引擎的序列化信息品种繁多,其中最为常见且内存占用较大的为 SerializedFile。该序列化信息的内存调配次要是我的项目通过特定 API(WWW.LoadFromCacheOrDownload、CreateFromFile 等)加载 AssetBundle 文件所致。
资源内存占用:次要包含 Mesh、AnimationClip、RenderTexture 等资源。对于未开启“Read/Write Enable”选项的 Mesh 资源,其内存占用是统计在 GFX 内存中供 GPU 应用的,但开启该选项后,网格数据会在 Reserved Unity 中保留一份,便于我的项目在运行时对 Mesh 数据进行实时的编辑和批改。同时,如果研发团队同样开启了纹理资源的“Read/Write Enable”选项(默认状况下为敞开),则纹理资源同样会在 Reserved Unity 中保留一份,进而造成其更大的内存占用。
(2)GFX 内存
GFX 内存为底层显卡驱动所反馈的内存调配量,该内存调配由底层显卡驱动所管制。一般来说,该局部内存占用次要由渲染相干的资源量所决定,包含纹理资源、Mesh 资源、Shader 资源传向 GPU 的局部,以及解析这些资源的相干库所调配的内存等。
(3)托管堆内存
托管堆内存示意我的项目运行时代码调配的托管堆内存调配量。对于应用 Mono 进行代码编译的我的项目,其托管堆内存次要由 Mono 调配和治理;对于应用 IL2CPP 进行代码编译的我的项目,其托管堆内存次要由 Unity 本身调配和治理。
1.2 内存参数规范
在咱们理解了内存相干的各项参数的含意之后,晓得了防止游戏闪退的重点在于管制 PSS 内存峰值。而 PSS 内存的大头又在于 Reserved Total 中的资源内存和 Mono 堆内存。对于应用 Lua 的我的项目来说,还应关注 Lua 内存。
依据 UWA 的教训,只有当 PSS 内存峰值管制在硬件总内存的 0.5-0.6 倍以下的时候,闪退危险才较低。举例而言,对于 2GB 的设施而言,PSS 内存应管制在 1GB 以下为最佳,3GB 的设施则应管制在 1.5GB 以下。
而对于大多数我的项目而言,PSS 内存大概高于 Reserved Total 200MB-300MB 左右,故 2GB 设施的 Reserved Total 应管制在 700MB 以下、3GB 设施则管制在 1GB 以下。
特地的,UWA 还认为 Mono 堆内存须要予以关注,因为在很多我的项目中,Mono 堆内存除了存在自身驻留偏高或存在泄露危险的问题外,其大小还会影响 GC 耗时。UWA 认为管制在 80MB 以下为最佳。
下表为 UWA 提供的细化到每一种资源内存的举荐规范,制订较为严格。不过,仍须要开发者依据本身我的项目的理论状况予以调整。比方某个 2D 我的项目节俭了简直所有网格资源的应用,那么其余资源的规范就能够放宽很多。
对于更多的细化规范,大家能够间接在 UWA 线上产品中进行对应查看。
基于我的项目实情制订内存规范后,个别需进一步与美术、策动协商,给出正当的美术标准参数,并撰写成文档。
定好标准后,定时查看我的项目里的所有美术资源是否符合规范,及时批改和更新。查看美术是否合规的过程,能够利用 Unity 提供的回调函数写自动化工具,提高效率。能够参考《自动化标准 Unity 资源的实际》。
如果资源若不能批量解决成高中低配版本,就须要美术为各个画质等级制作不同的资源。
1.3 本地资源检测服务 - 我的项目资源检测
各项资源内存的引擎设置项繁琐且并不都能在运行时被采集,下文行将提到的内容尽管是泛滥我的项目中常见且重要的问题,但理论我的项目中的状况更加简单。通过本地资源检测服务的我的项目资源检测界面,往往能发现更多资源设置项的问题。它们不光影响相干资源的内存占用,还会依据状况对 CPU 耗时和 GPU 造成不同水平的压力。
为此 UWA 依据教训设计了检测规定和阈值,以此为根据采集和统计了存在这些问题的资源,并给出了对应的优化倡议,帮忙开发者针对资源进行更加深刻的排查和优化。
2. 常见的共通性问题
这一部分提到的问题没有特定性,不仅仅呈现在一种资源内存中。所以,为了防止赘述,此处对立予以探讨。
2.1 疑似冗余景象
在 UWA GOT Online Resource 模式报告的具体资源列表(下文简称资源列表)中,咱们常能看到某一项资源的数量峰值大于 1 且被标红。数量峰值同样是资源应用中十分重要的一项指标。所谓“数量峰值”,是指同一资源在同一帧中呈现的最大数量。实践上,数量峰值这一参数不应大于 1,当数量峰值大于 1 时,列表中会将其标红,咱们称之为疑似冗余资源。
个别状况下,呈现这种问题是由 AssetBundle 资源加载导致的,即在制作 AssetBundle 文件时,局部共享资源(比方 Texture、Mesh 等)被同时打入到多份不同的 AssetBundle 文件中但没有进行依赖打包,从而当加载这些 AssetBundle 时,内存中呈现了多份同样的资源,即资源冗余,倡议对其进行严格的检测和欠缺。
针对排查出的疑似冗余景象,能够应用 UWA 在线 AssetBundle 检测工具排查是否的确存在 AssetBundle 冗余的问题,尽量减少 AssetBundle 的冗余。倡议依据冗余资源的内存大小来决定对冗余问题的优化优先级。
值得一提的是,所谓“疑似冗余资源”,是指在检测过程中,咱们尝试搜寻我的项目运行时的冗余资源并将其反馈给用户。然而,咱们并无奈保障该项检测的 100% 正确性。这是因为,咱们判断的规范是依据资源的名称、内存占用等属性(因资源类型不同可能有格局、Read/Write、时长等属性,以报告资源列表中出现的属性为准)而定,当两个资源的名称、内存占用等属性均统一时,咱们认为这两个资源可能为同一资源,即其中一个为“冗余”资源。但我的项目中的确也存在资源不同但各项属性都雷同的状况。因而,咱们将通过以上规定提取出的资源归为“疑似冗余资源”。所以,是否的确为冗余资源,还须要联合我的项目实情和在线 AssetBundle 检测报告能力下结论。
2.2 未命名资源
在资源列表中,有时发现存在资源名称为 N / A 的资源。一般来说名为 N / A 的资源都是在代码中 new 进去然而没有予以命名的。倡议通过.name 办法对这些资源进行命名,不便资源统计和治理。尤其是其中冗余比较严重的或者个别内存占用十分大的 N / A 资源应予以关注和严格排查。
2.3 常驻资源内存占用大
在资源列表中,有时联合资源的生命周期曲线发现,一部分自身内存占用较大的资源在被加载进内存后,驻留在内存中,直到测试流程完结都没有被卸载,可能造成越到游戏前期资源内存占用越大、峰值越高。倡议排查这些资源是否有常驻在内存中的必要。如果不再须要被应用,则应查看为什么场景切换时没有卸载;对于持续时间久的单场景中继续驻留的资源,则能够思考手动卸载。
对于资源是否常驻的考量波及内存压力和 CPU 耗时压力之间的取舍。简略来说,如果以后我的项目内存压力较大,而场景切换时的 CPU 耗时压力较小,则能够思考扭转缓存策略,在场景切换时及时卸载下一个场景用不到的资源,在须要时再从新加载。
3. 纹理资源
3.1 纹理格局
纹理格局设置不合理通常是造成纹理资源占据较大内存的次要起因之一。即使是对于很多曾经建设过美术资源规范并对立批改过纹理格局的我的项目而言,依然很容易统计到存在大量的 RGBA32、ARGB32、RGBA Half、RGB24 等格局的纹理资源。这些格局的纹理岂但内存占用较大,还会导致游戏包体较大、加载这些资源的耗时较高、纹理带宽较低等等问题。
呈现这类问题的起因次要有以下几种:存在一些“漏网之鱼”,比方美术命名不标准导致没有被回调函数批改,或者是代码中创立的资源没有设置其纹理格局;硬件或纹理资源自身不反对指标格局纹理,导致被解析为未压缩格局的纹理。
对于前一种状况,在资源列表中发现有问题的资源后,须要回到我的项目中自行排查批改;对于后一种状况,UWA 举荐的硬件反对的纹理格局次要有 ASTC 和 ETC2。
其中 ETC2 格局须要对应的纹理分辨率为 4 的倍数,在对应的纹理开启了 Mipmap 时更是严格要求其分辨率为 2 的次幂。否则,该纹理将被解析成未压缩格局。
3.2 分辨率
纹理资源的分辨率(即资源列表中的长度和宽度参数)同样也是造成内存占用过大的次要起因。一般来说,分辨率越高,其内存占用则越大。其中最为须要关注的是占据较大分辨率(个别为 ≥ 1024)的纹理。对于挪动平台来说,过于精密的体现通过玩家的肉眼很难分辨出差别,而过大的分辨率往往意味着不必要的节约。
在不同档位的机型上应用不同分辨率大小的纹理资源是十分实用且易操作的分级策略。这一点即使对于图集纹理也同样实用,特地地,Unity 针对 SpriteAltas 提供了 Variant 性能,能够快捷的复制一份原图集并依据 Scale 参数升高该变体图集的分辨率,以供较低的分级应用。
3.3 Read/Write Enabled
上文提到过,纹理资源的内存占用是计算在 GFX 内存中的,也就是传向 GPU 端的局部。而开启 Read/Write Enabled 选项的纹理资源还会保留一份内存在 CPU 端,从而造成该资源内存占用翻倍。
UWA GOT Online Resource 模式报告资源列表或是本地资源检测报告中都间接展现了哪些纹理开启了 Read/Write Enabled 选项。实际上,不须要在运行时进行批改的资源是不须要开启 Read/Write Enabled 选项的,开发者应排查并敞开不必要的设置从而升高内存开销。
3.4 Mipmap
当一张纹理开启 Mipmap 时,它的内存占用会回升为原始数据的 1.33 倍。对于 3D 对象,比方场景中的地形、物件或人物,其纹理的 Mipmap 性能是倡议开启的,能够在运行时升高带宽。但值得注意的是,在真人真机测试报告中的 Mipmap 页面中,统计了游戏过程中开启 Mipmap 纹理的各个 Mipmap 通道的屏占比变化趋势。如果场景中的 3D 物体大面积地应用 1 / 2 乃至 1 /4、1/ 8 的 Mipmap 通道,阐明该 3D 物体应用的纹理分辨率偏高,存在节约景象。能够改用更低分辨率的纹理。
但如果是 2D 我的项目或 UI 界面资源,则倡议将对应纹理的 Mipmap 性能敞开,从而防止不必要的内存开销。
3.5 各向异性与三线性过滤
开启纹理的各向异性滤波有利于高空等物体的显示成果,但会导致 GPU 渲染带宽回升。其中的原理是,纹理压缩采样时会去读缓存外面的信息,如果没读到就会往离 GPU 更远的中央去读 System Memory,因而所花的时钟周期也就会增多。当开启各向异性导致采样点增多的时候,产生 Cache Miss 的概率就会变大,从而导致带宽回升的更多。在引擎中能够通过脚本敞开纹理资源的各向异性;或者对于须要开启各向异性的纹理,引擎中能够设置其采样次数为 1 -16,也倡议尽量设为较低的值。
将纹理设置为三线性过滤,纹理会在不同的 Mipmap 通道之间进行含糊,相比双线性过滤 GPU 渲染带宽将会回升。三线性插值采 8 个采样点(双线性采 4 个采样点),同样会使 Cache Miss 的概率变大,从而导致带宽回升,应尽量避免应用三线性过滤。
这两种采样形式的纹理也会被 UWA 的本地资源检测统计和列举进去,供开发者排查。
3.6 图集制作
图集制作不够迷信也是我的项目中常会产生的问题。资源列表中有时会呈现数量峰值较高的图集纹理,但不肯定是冗余。一种状况是,大量小图被打包到同一图集中,导致该图集纹理资源设置的最大分辨率(比方 2048*2048)一张装不下这么多小图,该资源就会生成更多的纹理分页来打包这些小图。因而,只有游戏过程中依赖某一张纹理分页中的某一张小图,就会将该资源、也即该资源下所有的分页都全副加载进内存中,从而造成不必要的节约。所以个别倡议管制到 2 - 3 张分页以内较为正当。
即使不呈现上述这个较为极其的景象,很多我的项目中也会呈现“牵一发而动全身”的景象。即明明只用图集中的一张或几张小图,却将内存占用颇大的整个纹理都加载进了内存。
为此,在制作打包图集时,严格依照小图的应用场景、分类进行打包是十分重要的策略。选用适合的分辨率从而防止纹理没有被填充斥而导致节约,也是开发者须要留神的点。
3.7 应用 TextMeshPro 的状况
TextMeshPro 能为 UI 组件提供更好的体现和便当的性能,使得其受到不少开发者的青眼。但应用 TMP 而产生的 TMP 字体图集纹理(名称中带有 SDF Atlas,格局为 Alpha 8 的纹理)也有一些坑值得注意。
(1)有时,联合字体资源列表留神到内存中还存在 TMP 图集纹理对应的.ttf 字体文件。阐明该 TMP 字体图集为动静字体。能够思考在我的项目开发完结、确保游戏要用到的字符都已增加到动静字体的 Altas 纹理中后,将动静 TMP 从新设置为动态 TMP,并且解除对.ttf 文件的依赖。这样一来,对应的字体资源将不会呈现在内存中。不过,如果这种字体还被用作用户输出,则不倡议采纳此办法。
(2)Atlas 字体纹理的分辨率较大。此时倡议在引擎中排查字符有没有填满图集纹理,纹理的制作生成是否正当。对于动静 TMP,如果没有填满,如只占据了纹理的 3 / 4 不到,则能够思考开启 Multi Atlas Textures 选项,并设置纹理大小,举例而言就能够使 1 张 4096*4096 的纹理变为 3 张 2048*2048 的纹理,节俭 32MB-3*8MB=8MB 的空间。
(3)资源列表中有 TMP 相干的资源(LiberationSans SDF Atlas、EmojiOne),它们都是 TMP 的默认设置,能够在 Project Settings-TextMesh Pro Settings 中解除对这些默认资源的依赖,就不会呈现在内存中了。
因为 Multi Atlas Textures 是动静 TMP 的选项,所以(1)、(2)无奈同时应用,能够依据我的项目实情酌情选用。
3.8 应用本地资源检测排查纹理问题
在本地资源检测中蕴含了“应用非压缩格局的纹理”、“尺寸过大的纹理”、“开启 Read/Write 选项的纹理”、“开启 Mipmap 选项的 Sprite 纹理”、“开启各向异性过滤的纹理”、“过滤模式为 Trilinear 的纹理”等上文曾经提及的检测规定,不便开发者精确定位存在潜在性能问题的纹理资源。
4. 网格资源
4.1 顶点和面片数
顶点和三角形面片数过多的网格资源不仅会造成较高的内存占用,同时也不利于裁剪,容易减少渲染面数,在渲染时对 GPU 和 CPU 造成压力。针对这些网格,一方面能够简化网格,缩小顶点数和面数,制作低模版本,供中低端机型分级应用;而另一方面针对单个顶点数过高的动态网格,比方一些简单的地形和修建,能够思考拆分成若干个反复的小网格从新拼接。只有做好合批操作,就能以付出一点 Culling 计算耗时为代价,缩小同屏渲染面片数。
4.2 顶点属性
如果没有对立美术资源规范且在导入时没有进行解决,则我的项目中的网格很有可能蕴含大量“多余”的顶点数据。这里的“多余”数据是指网格数据中蕴含了渲染时 Shader 中所不须要的数据。举例而言,如果网格数据中含有 Position、UV、Normal、Color、Tangent 等顶点数据,但其渲染所用的 Shader 中仅须要 Position、UV 和 Normal,则网格数据中的 Color 和 Tangent 则为“多余”数据,从而造成不必要的内存节约。其中,一个小网格资源带有顶点属性,会使所在的 Combined Mesh 也带有顶点属性,须要予以留神。
针对这个问题,一个比较简单的办法是,尝试开启“Optimize Mesh Data”选项。该选项位于 Player Setting 的 Other Settings 中。勾选后,引擎会在公布时遍历所有的网格数据,将其“多余”数据进行去除,从而升高其数据量大小。然而,须要留神的是,对于在 Runtime 状况下有批改 Material 需要的网格,倡议研发团队对其进行额定的留神。如果 Runtime 时须要为某一个 GameObject 批改更为简单、须要拜访更多顶点属性的 Material,则倡议先将这些 Material 挂载在相应的 Prefab 上再进行公布,免得引擎去除 Runtime 中会进行应用的网格数据。
4.3 Read/Write Enabled
在资源列表中,经常统计到大量顶点属性不显示为 -1(或“-”)的网格资源。只有网格资源开启 Read/Write 时,UWA 报告能力采集到顶点属性信息。此时,顶点属性不显示为 -1,且会使得网格占用内存回升。一般而言,不须要在 CPU 端进行批改的网格是不须要开启 Read/Write 的。能够在编辑器中通过 API 批改这些网格的 Read/Write 属性,或者对于 FBX 中的网格能够间接在 Inspector 窗口中批改。
4.4 应用本地资源检测排查网格问题
在本地资源检测中蕴含了“面片数过大的网格”、“蕴含 Color 属性的网格”、“蕴含 Tangent 属性的网格”、“蕴含 Normal 属性的网格”、“蕴含 UV3 或 UV4 属性的网格”、“开启 Read/Write 选项的网格”等上文曾经提及的检测规定,不便开发者精确定位存在潜在性能问题的网格资源。
5. 动画资源
一般来说,内存占用大于 200KB,且时长较短的动画资源就能够被认为是内存占用偏大的动画资源,有肯定的优化空间。针对动画资源的优化办法有:
(1)将 Animation Type 改成 Generic。相比另一种 Legacy 类型,Generic 实际上应用了 Unity 新版的 Mecanim 动画零碎,整体性能要好很多,个别不倡议应用老版的动画零碎,而第三种 Humanoid 同样是新版动画零碎提供给兽性角色的非凡工作流,具备灵便复用性的长处,但对模型的骨骼数量有要求(即人形骨骼),能够依据我的项目须要选用。
(2)将 Anim. Compression 改成 Optimal。Optimal 实际上就是让 Unity 在数个算法中主动抉择最优的曲线表达方式,从而占用最小的存储空间。而 Keyframe Reduction 则是一个绝对稳固激进的算法,对动画的体现成果产生影响的概率更小。
(3)敞开 Resample Curves 选项。官网文档中称开启该选项会有肯定的性能晋升,但事实上依据《自动化标准 Unity 资源的实际》中的说法,上文提到的开启 Resample Curves 的性能晋升体现在播放时而非加载时、且成果微不足道;反倒是还可能造成谬误的动画体现。所以联合试验数据,大部分状况下,这个选项是倡议敞开的。
(4)思考应用 API 剔除动画资源的 Scale 曲线和压缩动画的精度。其中,压缩动画精度的做法能够参考《Unity 动画文件优化探索》。
以上四种办法都能够无效升高动画资源的内存占用,但(2)、(4)两种实践上会造成动画精度的损失,但不肯定会看得出来。倡议研发团队自行调试,在确保动画体现不受影响的状况下尽量优化其内存占用。
在本地资源检测中蕴含了“Compression != Optimal 的动画资源”、“动画的导入设置未敞开 ResampleCurve”、“蕴含 Scale 曲线的动画片段”、“精度过高的动画片段”等上文曾经提及的检测规定,不便开发者精确定位存在潜在性能问题的动画资源。
6. 音频资源
对于时长较长的 BGM 和一些惯例的时长较短但内存大的音频资源,有肯定的优化空间。针对音频资源的优化办法有:
(1)开启 Force To Mono。开启音频资源的 Force To Mono 会使音频被主动混合为单声道,而并非失落一个声道,从而在对体现成果影响较小的前提下大幅升高音频内存。
(2)批改其加载形式(Load Type)为 Compressed In Memory 或 Streaming。Compressed In Memory 实用于大部分惯例音频,而 Streaming 则适宜时常较长且内存占用大的背景音乐。
(3)对于 Compressed In Memory 的音频,批改其压缩格局(Compression Format)为压缩率更大的格局,如 Vorbis、MP3;
(4)对于 Vorbis、MP3 压缩格局的音频,还能够持续调低其 Quality 参数,进一步压缩其内存。
以上形式都能够无效缩小音频资源内存(其中 Streaming 能够稳固降至 200KB 左右),但会造成肯定的耗时代价或音质升高,能够酌情选用。
在本地资源检测中蕴含了“双声道的音频”、“未应用 Streaming 加载的长音频”、“该音频中应用了 Quality 过高的 Vorbis 与 MP3 压缩”等上文曾经提及的检测规定,不便开发者精确定位存在潜在性能问题的音频资源。
7. 材质资源
材质资源自身内存占用较小,咱们个别更加关注如何优化其数量,因为它的数量过多会影响之后会提到的 Resource.UnloadUnusedAssets API 的耗时。
材质资源数量过多,往往次要是因为 Instance 类型的冗余 Material 资源过多。一般来说,该种状况的呈现是因为通过代码拜访并批改了 meshrender.material 的参数,因而 Unity 引擎会实例一份新的 Material 来达到成果,进而造成内存上的冗余。对此,倡议通过 MaterialPropertyBlock 的形式来进行优化,具体相干操作和例子见如下文章《应用 MaterialPropertyBlock 来替换 Material 属性操作》。不过这种办法在 URP 下不实用,会打断 SRP Batcher。除此之外,则须要关注和优化非 Instance 的材质资源的疑似冗余景象,不再赘述。
除了数量上的问题外,材质资源往往还波及到一些纹理采样和 Shader 应用相干的问题,导致一些额定的内存和 GPU 性能节约,而其中比拟值得关注的也曾经作为检测规定统计在 UWA 本地资源检测报告中。
对于应用纯色纹理采样的材质,能够将纹理采样替换为一个色彩参数,从而节俭一张纹理采样的开销;而对于空纹理采样的材质,Unity 会采样内置提供的纹理,然而计算失去的色彩是一个常数,依然属于节约;又对于蕴含无用纹理采样的材质,因为 Unity 的机制,材质球会主动保留其上的纹理采样,即便更换 Shader 也不会把原来依赖的纹理去除,所以可能会造成误依赖理论不须要的纹理带进包体的状况,从而造成内存的节约。
8. Render Texture
8.1 渲染分辨率
资源列表中的一些 RT 资源能反映我的项目以后的渲染分辨率。对于 GPU 和渲染模块压力较大的我的项目,在中低端机型上升高其渲染分辨率是十分直观无效的分级策略。个别低端机型上能够思考不采纳真机分辨率,降到 0.8-0.9 倍,甚至很多团队会抉择 0.7 倍或 720P。
如果一些其余的 RT 资源分辨率过高也应引起留神,尤其是 2048*2048 以上的资源。该当排查是否有必要用到如此精密的 RT,在低端机上思考采纳更低分辨率的成果。
8.2 抗锯齿
资源列表中展现了 RT 资源的 AA 倍数。开启多倍 AA 会使 RT 占用内存成倍回升,并对 GPU 造成压力。倡议排查是否有必要开启 AA,尤其在中低端机上,能够思考敞开此成果。
特地的是,在华为局部机型上 2 倍的 AA 会生效。即曾经造成了性能耗费但没有理论起到抗锯齿成果。
8.3 后处理
一些常见的后处理相干的 RT(如 Bloom、Blur)是从 1 / 2 渲染分辨率开始采样,能够思考改从 1 / 4 开始采样、并缩小下采样次数,从而节俭内存并升高后处理对渲染的压力。
站在性能优化的角度,在中低端机型上甚至最好齐全敞开各类后处理。围绕一些常见后处理成果的讨论会在下文 GPU 局部中进一步开展。
8.4 URP 下的 RT
应用 URP 时,内存中会多出_CameraColorTexture 和_CameraDepthAttachment 两份 RT 资源作为渲染指标,而开启 URP 相机的 CopyDepth 和 CopyColor 设置时会额定产生_CameraDepthTexture 和_CameraOpaqueTexture 作为两头 RT。当资源列表中呈现这两种 RT 时,须要排查的确是否用到 CopyDepth 和 CopyColor,否则应予以敞开以防止不必要的节约。
9. Shader 资源
9.1 ShaderLab
Unity 2019.4.20 是 Shader 内存统计办法的一个转折点。在此之前,Shader 的内存次要统计在 ShaderLab 中,而之后则次要统计在 Shader 资源本身身上。
对于 Unity 2019.4.20 之前的版本的我的项目,查看 ShaderLab 的内存须要在 Unity Profiler 中 TakeSample。无论是 Shader 资源本体还是 ShaderLab 内存占用过高,都要着手于管制 Shader 的数量和变体数量。
9.2 变体数
变体数过多是造成一个 Shader 资源内存占用过大、占用包体过大的次要起因。在我的项目迭代中可能会呈现曾经被弃用或者没有被理论应用到的关键字,导致变体成倍回升;又或者 Shader 写的比较复杂,其中一些关键字组合永远不会被用到,从而导致很多变体是多余的。UWA 的本地资源检测中提供了 Shader 检测性能,能够看到变体数量,定位变体数过多的 Shader 资源。
针对上述情况,Unity 提供了回调函数,在我的项目打 AssetBundle 包或者 Build 时线剔除用不到的关键字或关键字组合相干的变体。剔除 Shader 变体的办法能够参考《Stripping scriptable shader variants》。
9.3 冗余
Shader 冗余尤其须要予以关注,Shader 的冗余不光导致内存回升,还可能造成反复解析,即运行时不必要的 Shader.Parse 和 Shader.CreateGPUProgram API 调用耗时。
9.4 Standard Shader
在资源列表中发现 Standard、ParticleSystem/Standard Unlit。这两种 Shader 变体数量多,其加载耗时会十分高,内存占用也偏大,不倡议间接在我的项目中应用。呈现的起因个别是导入的 FBX 模型中或者 Unity 本身生成的一些 3D 对象应用了自带的 Default Material,从而依赖了 Standard Shader,倡议予以排查精简。也能够联合 UWA 在线 AssetBundle 检测工具排查是哪个 AssetBundle 包中哪些资源援用了 Standard Shader 和 ParticleSystem/Standard Unlit。如果的确要应用 Standard Shader 或 ParticleSystem/Standard Unlit,应思考本人重写一个 Shader 并只蕴含本人须要用到的变体。
9.5 应用本地资源检测排查 Shader 问题
在本地资源检测中蕴含了“我的项目中:全局关键字过多的 Shader”、“我的项目中:可能生成变体数过多的 Shader”、“Build 后:生成变体数过多的 Shader”、“应用了 Standard Shader 的材质”等上文曾经提及的检测规定,不便开发者精确定位存在潜在性能问题的 Shader 资源。
10. 字体资源
若单个字体资源内存占用超过 10MB,能够认为该字体资源内存偏大。能够思考应用 FontPruner 字体精简工具或其余字体精简工具,对字体进行瘦身,减小内存占用。
咱们也须要关注我的项目中字体数量过多的状况,因为每个 Font 会对应一个 Font Texture 字体纹理,所以字体资源数量多了,Font Texture 的数量也多了,从而占用较多内存。
11. 粒子系统
将资源列表联合粒子系统曲线来看,很多我的项目的内存中粒子的数量会远远高于理论 Playing 的粒子数量。
此时一方面须要查看是否是在迭代过程中有被弃用但未删除的粒子资源或制作过程中测试过的组件但未解除依赖;另一方面则能够思考优化对粒子的缓存策略,缩小不必要的粒子缓存。
12. Mono 堆内存
UWA GOT Online Mono 模式报告提供了堆内存具体调配和堆内存泄露剖析两个次要性能,供开发者剖析我的项目中堆内存存在的问题。
12.1 继续 / 峰值调配堆栈
在堆内存具体调配页面中,能够排查高堆内存调配函数的具体堆栈。咱们次要关注两种模式的堆内存调配。
一种是单次过高的堆内存调配。这种峰值个别呈现在游戏初期的读表操作导致的大量调配,须要开发者联合具体堆栈信息排查是否正当。而游戏运行过程如果还呈现堆内存调配峰值则须要着重关注。
另一种则是继续偏高的堆内存调配。如果我的项目中存在每帧或者每隔几帧就调配较多堆内存的景象须要引起留神。继续的高堆内存调配会导致 GC 频率增高,从而在游戏中造成频繁的卡顿,能够联合堆栈排查是什么子节点在继续调配堆内存。
12.2 泄露剖析
在泄露剖析页面中排查我的项目中各个函数的堆内存驻留状况。选中图表中前后两处采样帧进行比拟,就能够从堆栈中查看堆内存驻留状况的变动,查看驻留回升次要是什么堆栈调配造成的。
一方面能够防止堆内存持续上升造成泄露的危险,另一方面针对驻留高的函数进行优化,予以及时开释,能够升高单次 GC 的耗时。咱们个别举荐测试 GOT Online Mono 模式的测试时长尽量长一些,比方 1 个小时,否则泄露问题往往难以被裸露。
13. 其余内存
13.1 Lua
UWA GOT Online Lua 模式提供了针对 Lua 脚本语言的性能测试。
其中呈现的函数名称格局为:函数名称 @文件名:行号。
能够通过报告提供的 Lua 文件名 / 行号 / 函数名来定位 CPU 耗时的瓶颈函数和 CPU 耗时峰值的具体起因。Lua 函数的命名格局为 X@Y:Z,其中 X 是其函数名,在无奈获取时,X 会变为默认的 unknown;Y 是该函数定义的文件地位;Z 则是该函数被定义的行号。须要留神的是,当 Lua 脚本以字节码运行时,该值将始终为 0,因而倡议在测试时尽可能应用 Lua 源码来运行。
针对 Lua 调配的内存,报告中的折线图选取了 30 帧内的数据最大值作为数据点。依据折线图走势,帮忙开发者对我的项目运行过程中的堆内存分配情况有大抵的理解。其中,堆内存的降落意味着产生了一次 GC。查看内存具体调配和泄露剖析和性能和 Mono 模式报告大同小异。
Lua 模式报告中还有一个重要性能,即 Mono 对象援用统计。
从原理层面上,Unity Mono 虚拟机中保护了一个对象池,用于链接 Unity Object 对象和 Lua 对象。当场景中的 Unity Object 对象被 Destroyed 之后,场景中没有了,然而因为 Lua 层还持有 Usedata 援用,导致对象池无奈开释该 Unity Object,如果该对象援用了 Texture、Mesh 等相干资源,会造成泄露。这时须要将 Lua 层的相干对象置空(nil),解除援用后,在下一次 GC 产生后,就能够回收该 Unity Object 对象。该性能的意义就在于辅助开发者排查此类泄露危险。
报告提供了 Mono 对象援用柱状图,其中彩色局部示意未被 Destroyed 的对象数目,因为受到 Lua 端 GC 的影响,导致会有一些 Destroyed 对象。这时候就要留神它是否是趋于稳定的,如果继续上涨就须要引起器重。
在柱状体抉择对应帧后,列表中会显示该帧的 Mono 对象类型列表。其中:
对象类型:示意 Unity Object 对象的具体类型;
对象个数:示意这种类型的对象个数;
Destroyed 对象个数:示意曾经被 Destroyed,但 Lua 层还有相干援用的这种类型的对象个数。须要关注 Destroyed 对象个数,如果数目较大,C# 堆内存存在泄露危险。
13.2 插件和第三方库
Wwise 等插件和第三方库的应用相当广泛,但个别无奈在运行时定量直观地统计。不过个别它们占用的内存不大,只有在上文这些内存优化点都排查结束后依然发现 PSS 内存和 Reserved Total 的值之间有加大差距时,再联合插件或第三方库的文档或其开发者提供的办法进行针对性优化,甚至思考采取性能更优的代替计划。
本文内容就介绍到这里啦,更多内容能够返回 UWA 学堂进行浏览。课程将从内存、CPU、GPU 三个维度探讨以后游戏我的项目中经常出现的一些性能问题。