找回密码
 立即注册
查看: 506|回复: 0

[笔记] Unity内存与资源管理(一)

[复制链接]
发表于 2021-12-8 14:10 | 显示全部楼层 |阅读模式
内存管理是程序开发的核心问题,而资源的使用又与内存息息相关,因此本章想要梳理整个开发流程中Unity对于内存与资源的管理方式。
一、内存基础

1.1 基础概念
内存是暂时存放CPU中的运算数据,与硬盘等外部存储器交换的数据。在操作系统中,内存分为物理内存与虚拟内存。
CPU读取数据
CPU进行数据处理时,从内存或缓存中取出指令,放入指令寄存器,并对指令译码进行分解,进而对数据进行处理。从内存中读取数据很慢,通常CPU会将之前读取的数据缓存在多级Cache中,提升数据访问效率。因此,CPU会先从Cache中查找数据,若没有找到(Cache Miss),才会访问内存。


物理内存:物理内存可以理解为运行时内存,其对应于实际的存储硬件。
虚拟内存:管理不同进程使用的进程内部内存地址和物理内存地址的映射体系。
页:内存管理、映射中的基本单位。
内存交换(页面交换):当系统内存不足时,操作系统会创建交换文件(Swap file)或者分页文件(Paging file)记录暂时不用的物理内存页面中的数据,并将这部分数据交换到硬盘上,节省更多的物理内存。
1.2 移动设备与PC差异

  • 没有独立显卡,显存与CPU数据会存储在同一区域。
  • CPU的Cache与内存更小。因此移动端会严格限制资源大小。
  • 没有内存交换(IO速度慢、可擦写次数少)
  • IOS可以进行内存压缩:将不活跃的内存压缩到特定空间。
1.3 Android内存分布


上图可以看出运行时内存包括:方法区、堆栈以及线程内存。
Android中对内存有4种统计方式:VSS(虚拟内存)、RSS(实际物理内存:APP+所有共享内存)、PSS(APP+平均共享库内存)、USS(APP内存)。通常使用PSS来统计。
Android对进程的管理策略称为LMK(Low Memory Killer),kill优先级如下:



1.4 IOS内存分布



IOS内存分布

Clear Memory包括系统框架数据、可执行的二进制数据以及内存映射文件。Clear Memory以外的内存都是Dirty Memory。
当系统存在内存压力时,会先卸载一部分Clear Memory,当再次需要时会对其重新创建。但系统无法对Dirty Memory进行卸载,直到到达App的限制才会被终止回收。内存不足时,IOS会对Dirty Memory做压缩处理。
IOS中Resident Memory指的是App分配的物理内存。当App向系统申请内存时,虚拟内存是直接增长的。但如果申请完的内存并没有向里面写入数据,它并不会产生实际的物理内存分配。
iOS的进程管理策略称为Jetsam,GitHub有人测试了其kill进程的优先级:



Jetsam优先级

二、Unity内存与GC

从Editor与Runtime比较,在Runtime时,只有Load资源才会影响内存,而Editor模式下,为了方便开发,只要打开Unity资源就会被加载(Unity打开很慢,2019.3的Asset Pipeline 2.0做了优化)。
2.1 Mono与IL2CPP的跨平台
Mono与IL2CPP是Unity的两种跨平台解决方案,目前官方推荐使用IL2CPP。



Mono跨平台流程

Mono的跨平台方案:编译器mcs将代码编译为IL,通过Mono运行时中的编译器将IL编译成对应平台的原生码。
Unity Mono是针对每个特定平台做了处理,因此才能跨平台。若出现新的硬件平台,则需要额外造轮子,其可移植性很差,因此Unity给出了IL2CPP方案。并且IL2CPP相比Mono有一定的性能提升。



IL2CPP方案流程(来自官方文档)

编译工程时,IL2CPP将Unity Scripting API 代码编译为常规 .NET DLL(托管程序集)。将所有托管程序集转换为标准C++代码。使用本机平台编译器编译生成的C++代码和IL2CPP的运行时部分。最后将代码链接到可执行文件或 DLL,具体取决于目标平台。
IL2PP相比于Mono,最大的变化在于由JIT编译变为了AOT编译。由此带来的变化体现在:(1)开发编译时间变长;(2)无法动态生成代码;(3)应用程序体积变小;(4)应用程序启动时间短。
在开发过程中可以选择Mono,提升开发效率,发布应用时使用IL2CPP得到更好的程序性能。
2.2 Unity VM GC
当Unity VM需要分配内存时,会检查当前内存。若内存足够时,可以直接分配内存,但内存不足时会触发GC,再次检测内存。若此时内存仍然不足,VM会向操作系统申请内存。
触发GC时,VM会暂停所有线程,可能引起程序卡顿。Unity采用了BOEHM处理GC,其为标记清除法。在标记阶段通过访问根节点,并遍历到叶子节点,最终将所有存在引用的内存都标记出来,清除未被标记的托管内存。
Unity的VM以Block管理内存,当一个Block连续6次GC没有被访问到(很难触发),这块内存会被返回给系统。
由于此种处理方式没有内存压缩,并且可能出现内存黑名单,因此容易出现内存碎片。在开发过程中,要尽量减少内存的申请,申请的内存尽量保持连续,对于资源加载应该优先加载较大的资源。
2.3 Unity内存分布



Unity内存分布

Unity内存分为Native堆内存、第三方库、虚拟机以及GfxDriver。Native堆内存中存储由Unity底层管理的数据,如音频、视频、Texture、Mesh、GameObject等。第三库常见的如tolua、Wwise。虚拟机通常指C#所产生的内存。GfxDriver是指当前驱动使用的Textures、RenderTargets、Shaders和Mesh数据内存。
三、Editor Asset




Unity 中的资源工作流程(来自官方文档)

Unity中的资源通常是指3D模型、音频文件、图像等用于程序使用的数据(代码数据也是一种资源)。程序开发阶段,将外部资源导入或复制到Asset目录下,Unity会监听到所导入的数据,并生成相应的Unity默认资源。
3.1 创建asset
Unity检测到新资源(文件夹)时会向此资源分配GUID,这是Unity在内部使用的ID,用于索引资源,以便Unity移动或重命名此资源不会破坏引用关系。
Unity对导入的外部文件格式不会进行修改,对内置格式资源(prefab/material/unity等),Unity以YAML格式进行存储。



prefab在Unity中的存储格式

文件前两行是文件格式版本注释,后续以--- !u!classID &fileID对组件数据进行标记。classID是Unity为组件类型分配的id,fileID为prefab中组件实例化的局部id,由此可以进行索引。上图GameObject挂载了3个组件,所以可以看到m_Component通过fileID映射到相应的组件实例。由于EventSystem是自定义组件,由此可以看到m_Script通过guid来引用到相应的代码资源。
由于这种格式化的存储方式,在非Unity Editor情况下处理或者查找带有特定参数的资源,可以通过纯文本替换或查找处理。(需要以下设置)



以文本格式存储序列化资源

3.2 监听资源导入
Unity提供了用于监听资源导入的基类,方便开发者对资源做标准化处理,具体基类如下:



来自Unity用户手册

3.3 资源处理
Unity在导入资源后,会在Library文件夹下生成真正用于Unity处理的资源。例如,Unity 将 .png 图像文件导入为纹理时,在运行时不会使用原始的 .png 格式数据,而是创建新的图形格式,将其存储在项目的Library文件夹中。Unity的Texture类会使用此导入版本,然后Unity将其上传到GPU进行实时显示。
除了Library,Unity会在Asset文件夹相同位置生成资源的.meta文件,用以记录资源设置参数与GUID。



meta文件

在Unity 2020以前的版本可以通过guid,在Library下搜索到Unity生成的资源。在Unity 2020之后的版本,会在Library下生成ArtifactDB和SourceAssetDB文件。
SourceAssetDB包含.meta相关数据(上次修改日期、文件内容哈希、GUID和其他元数据信息),由此判断是否需要重新导入资源。
ArtifactDB包含每个源资源的导入结果的信息。每个Artifact都包含导入依赖项信息、Artifact元数据信息和 Artifact文件列表。
从上述资源加载流程来看,项目中经常遇到资源引用丢失:(1)美术资源与meta没有一起上传,导致prefab无法通过guid找到资源。(2)美术替换资源时,先删除原有资源,再导入创建新资源,导致原有的prefab引用的guid失效。
对于漏传meta的情况,可在提交版本文件时做强制检测,若Asset下新增资源必须要有对应的meta文件。对于美术资源的替换,不能在Unity Editor下直接删除文件,需要通过import替换,或者在文件夹中替换资源再打开Unity。

参考

  • [Unity 活动]-浅谈Unity内存管理_哔哩哔哩_bilibili
  • Unity内存详解
  • 你一定要知道的iOS 内存管理|干货满满
  • What is resident and dirty memory of iOS?
  • https://github.com/conradev/jetsamctl
  • EnjoyMoving:Java内存模型(JMM)总结
  • 放牛的星星:Unity游戏内存分布概览
  • 为什么在 CPU 中要用 Cache 从内存中快速提取数据?
  • Windows 8或者Windows10的任务管理器中,内存已提交的两个数字都是什么意思?
  • IL2CPP 的工作原理 - Unity 手册
  • Unity - Manual: Memory in Unity
  • Testplus:解读MONO内存管理和回收
  • 资源工作流程 - Unity 手册

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-10 09:59 , Processed in 0.098600 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表