性能优化(32)——内存优化(1)——Unity中的内存概述与工具方法


 

Unity引擎中的内存

  • 托管内存:包括托管堆内存、脚本堆栈内存与虚拟机内存三部分。其中托管堆内存是优化的主要部分。托管内存使用方便,但释放与内存分配方式不可预期。另外,Unity使用的是保守的贝姆垃圾回收器(Boehm–Demers–Weiser garbage collector),在内存的管理与分配上并不会太激进,因此一些使用不当会造成内存的浪费,也可能会有性能的问题
  • C#非托管内存:是指在C#层不使用垃圾回收器管理的内存部分,它允许访问Native内存层,可以进行内存分配的微调,一般在C#下使用Unity Collection名字空间与包结合使用,也是将来使用DOTS程序加速准备的数据结构访问的内存.所以如果你的项目刚立项,如果使用数据结构,不建议使用System下Collection的数据结构,而推荐使用Unity Collection下的数据结构进行开发

  • 除红色Unity中的内存之外,Unity的应用程序还包括青色部分渲染资源占用的显存,如果是移动平台,该显存是与内存共享的;还包括蓝色部分应用程序框架所需要的内存、一些第三方库占用的内存以及操作系统为应用分配的内存。蓝色部分是不能通过Unity引擎提供的工具获取的,不过我们可以借助一些操作系统工具、编译工具或第三方工具进行抓取

Unity引擎提供的内存方面的工具

Unity提供的

  • Unity Profiler
    • Unity Profiler下的Memory标签,这里列举了Unity当下内存使用的追踪状态,包括各类内存的分配与使用情况,以及当前Unity下分配的对象与资源占用的内存情况
    • 注意:Unity2021以后的版本,Profiler不再提供对象抓取快照功能了,而是使用Memory Profiler直接抓取内存快照了
    • 此外,Profiler的Menory标签下默认提供的指标并不全,如果希望得到更详细的信息,可以通过添加自定义Profiler Modules加入更多的Memory Counter指标,也可以扩展Profiler Modules,通过对应的系统接口,提供更详细的内存指标加载到Profiler中

  • Unity Memory Profiler
    • 可以通过它抓取内存快照,也可以对比两个内存快照下,Unity对象与资源的差异
    • 通过Tree Map查看内存分配的视图
    • 通过Object and Allocations标签查看具体对象内存快照,并可以通过链接直接找到原工程中对应的资源或对象
    • 通过Fragmentation标签,可以查看内存片段。Unity2022以后,Memory Profiler变得更加简洁易用了,针对Native内存,甚至可以查看到具体是分配到哪个Allocate中的

  • 内存相关设置
    • 自Unity2021后,Unity开放了针对于Native层的内存分配自定义设置功能,用户可以针对自己项目的特定,自定义相关内存的Allocate分配块大小.大家现在可以在权衡时间与空间维度上的性能指标做更精准的设置了
    • 此外,与内存相关的设置还有一些在工程设置中和图像设置中
  • UPR中的内存快照与对象快照功能
    • 主要是针对于移动设备,当你使用UPR做性能调试时会经常用到
    • 它可以脱离Unity编辑器,在运行时抓取内存信息与对象分配信息,并可以做到多帧对比比较
    • 具体使用方法都是通过快捷键完成的,可以参阅UPR手册

Unity外的工具

  • Mac或IOS上可以选择XCode提供的Instruments下的Allocations工具
  • 安卓上可以使用安卓相关的系统命令,或安卓Studio的Profiler工具,或者一些第三方工具,但一般不太好用
  • 一般我们还是先在IOS上进行内存优化
  • Allocations工具:
    • 使用profiling运行XCode工程,当编译完成后,在profiling模板界面选择Allocations工具,并启动录制,也可以直接启用Instruments工具
    • 直接选择Allocations工具,并挂载手机上要测试的项目的进程,你也可以长按录制功能键修改录制选项,一般情况下需要在启动前选择下VM Tracker标签,选择完成后,XCode下会出现一个Snapshots按钮,在其中勾选Automatic Snapshotting选项,只有勾选了这个选项,才能看到VM Tracker下的信息
    • 点击录制按钮拉起我们的应用程序,这时会看到Allocations与VM Tracker中会有曲线变化,这代表我们正在录制中
    • 我们可以多测试下游戏中各个场景间的切换,观察曲线变化
    • 完成后可以选择停止选项,这时录制信息并不会丢失,但其信息内容会非常多,这里只挑我们最需要关注的指标进行说明:
      • VM Tracker下的Dirty Size、Swapped Size、Resident Size
        • Resident Size代表使用的物理内存量
        • Swapped Size代表不活跃的内存,可以被交换到磁盘上的大小。可以理解为可卸载的大小。在IOS中,只有非Dirty的内存页才可以被交换或卸载
        • Dirty Size代表物理内存中不能被复用或者被卸载的内存块。这个数值非常重要,一旦超过一定大小,IOS程序就会自动退出,这个大小在1G内存的手机上大概是700MB,2G内存的手机上大概是1.4G,这个数值直接关乎我们的应用程序在长期运行时会不会崩溃,所以需要关注它的峰值是否超过警戒线,并且有没有下降的过程
    • 接下来我们可以将Statistics标签切换成Call Trees,并在下面CallTree按钮中选择按线程分类,这样会方便我们查看具体那些线程做了哪些分配情况。如果发现线程堆栈是16进制地址,这可能是苹果系统或XCode版本造成的,只需要点击Date Mining中的Restore即可恢复
    • 完成这些后,我们可以看到渲染线程、主线程、Unity的加载线程的内存排在前三,可以在时间轴框选我们需要采样、查看内存分配情况的时间段来进一步看这个时间段各个线程的分配情况