Arzie100 发表于 2022-11-19 11:20

Unity内存管理机制笔记--杨沁七


[*]内存管理基础

[*]物理内存

[*]CPU访存速度慢:先Cache;再内存
[*]Cache Miss会导致大量的内存和Cache的IO交换,浪费大量时间

[*]Unity提出ECS方案来降低Cache Miss的概率







[*]台式设备和移动设备架构的差异

[*]移动设备没有独立显卡
[*]移动端数据内存和显存共享同一块内存
[*]移动设备的CPU面积小,导致缓存级数少,大小也更小







[*]虚拟内存

[*]定义:利用磁盘空间虚拟出一块逻辑内存,这一块磁盘空间被称为交换空间
[*]内存交换操作:操作系统在内存不足时,将一些不用的内存交换到硬盘上

[*]移动设备没有这个操作

[*]移动设备IO速度很慢
[*]移动设备的可存储物的可擦写次数比硬盘少很多,会减少使用寿命









[*]内存压缩:IOS中将不活跃的内存压缩起来存储到一个特定空间,以节省出物理内存空间
[*]内存寻址范围

[*]定义:CPU对于内存寻址的能力
[*]范围与Memory Controller(内存控制器)有关,与运算位数(64 or 32)无直接关系







[*]总结:在移动设备中需要更加高效的内存管理机制

[*]实际物理内存小,没有虚拟内存
[*]缓存级数少,大小也更小





[*]移动设备的内存管理

[*]Android内存管理

[*]基本单位Page:4K
[*]用户态和核心态
[*]内存杀手:Low Memory Killer当内存不足时,会清理内存
[*]Android应用分层
[*]内存指标

[*]RSS:独占+Services
[*]PSS:独占+均摊的Services
[*]USS:独占







[*]IOS内存管理



[*]Unity内存管理

[*]Unity是一个C++引擎

[*]最底层:Runtime,全是Native C++代码
[*]最上层:C#,例如Unity的Editor和部分Package
[*]中间层:Binding,将C#与C++联系起来,为C#提供API
[*]虚拟机:用于跨平台

[*]Mono

[*]目标:在尽可能多的平台上能正常运行.net标准程序
[*]组成:C#编译器、CLI虚拟机和核心类别程序库
[*]工作流程
[*]首先,通过C#编译器mcs,将C#编译为IL(中间语言,byte code)
[*]然后通过Mono运行时即VM中的编译器将IL编译成对应平台的原生码
[*]原生码是什么???











[*]三种转译方式
[*]即时编译(just in time,JIT):在程序运行过程中,将byte code转译为目标平台的原生码
[*]将IL代码转为对应平台的原生码并映射到虚拟内存中执行。编译时IL是依托Mono运行时,转为对应原生码后再依托本地运行
[*]为什么是虚拟内存











[*]提前编译(ahead of time,AOT):在程序运行之前,将.exe或.dll文件中的CIL的byte code部分转译为目标平台的原生码并且存储,程序运行中仍有部分CIL中的byte code需要JIT编译
[*]完全静态编译(full ahead of time,full-AOT):在程序运行之前,将所有源码编译成目标平台的原生码
[*]在程序运行之前编译是否也依托于Mono运行时???










[*]特点
[*]构建应用非常快
[*]Mono的JIT机制可以支持更多的托管类库
[*]支持运行时代码执行
[*]必须将代码发布成托管程序集(.dll文件,由mono或者.net生成)
[*]Mono VM在各个平台移植异常麻烦,有几个平台就得移植几个VM(WebGL和UWP只支持IL2CPP)
[*]Mono版本受限,C#很多新特性无法使用
[*]IOS仍然支持Mono,但不允许32位










[*]IL2CPP

[*]组成
[*]AOT(静态编译):将IL中间语言转换成CPP文件
[*]运行时库:例如垃圾回收、线程/文件获取(独立于平台,与平台无关)、内部调用直接修改托管数据结构的原生代码的服务与抽象










[*]特点
[*]转成CPP后运行效率更快,且可以调试生产的C++代码
[*]Mono VM在各个平台的移植和维护非常耗时
[*]利用现成的在各个平台的C++编译器对代码执行编译器优化,这样可以进一步减少最终游戏的尺寸并提高游戏运行速度
[*]动态语言特性,所有内存分配和回收由GC组件完成
[*]尽管通过IL2CPP后代码转换成了静态的C++,但内存管理还是遵循C#的方式,因此还需要一个IL2CPP VM,它来提供诸如GC管理、线程创建这类服务性工作
[*]IL2CPP可以做得很小,因为它不需要处理IL加载和动态解析,因此使得游戏载入时间缩短
[*]多平台移植非常方便
[*]相比Mono构建应用慢
[*]只支持AOT编译








[*]内存管理分类

[*]按照分配方式

[*]Native Memory(原生内存):不会被系统自动管理,需要手动释放

[*]Unity重载了C++的分配内存的操作符
[*]使用操作符时提供参数Memory Label指示将当前的内存分配到哪一个内存池
[*]Unity在底层使用Allocator,每个Allocator池,单独做跟踪
[*]NewAsRoot:使用Allocator的生成会使用NewAsRoot,生产Memory Island,它包括很多子内存
[*]返还系统:当使用delete和free内存时,会立刻返还给系统;这与托管内存不同,它需要GC才返回










[*]Managed Memory(托管内存):系统自动管理,通过GC释放

[*]VM内存池:即Mono虚拟机的内存池,以Block的形式管理,当一个Block连续6次GC没有被访问到,这块内存会被返回给系统
[*]GC分类
[*]分代与非分代式
[*]非分代式:全都堆在一起,速度快
[*]分代式:划分不同区域,包括大内存、小内存和超小内存










[*]压缩和非压缩
[*]非压缩式:有内存被释放,这块区域空着
[*]压缩式:对空白区域重新排布,填充空白,使内存紧密排布










[*]Incremental GC:解决原来GC导致的卡顿问题,将GC操作分帧进行










[*]Unity采用Boehm GC,简单粗暴,属于非分代、非压缩式,会导致内存碎片化









[*]这个如何控制?

[*]堆栈(Stack)
[*]存储函数和值类型的地方
[*]堆栈回溯
[*]不会出现碎片化或GC问题,但可能会碰见堆栈溢出










[*]堆积(Heap)
[*]引用类型
[*]销毁对象后,内存空间不会马上释放,而是标记为未使用,之后由GC释放
[*]对象实例化和摧毁过程很慢;可能会导致碎片化
[*]错误引用,导致希望释放的内存未被正常释放,造成内存泄漏
[*]当内存不足以分配给一个存储占用较大的对象时
[*]GC运行,尝试去释放更多的空间以满足对象的内存分配需求
[*]堆空间拓展










[*]官方文档的说明

[*]分为三层进行管理
[*]托管内存(Managed memory)
[*]重点在于托管堆(managed heap)和GC
[*]由Mono和IL2CPP‘s脚本虚拟机实现,也叫做脚本内存系统
[*]托管堆(managed heap):由虚拟机通过GC自动管理
[*]脚本栈(scripting stack):程序运行过程中的值类型和函数调用使用的内存
[*]原生虚拟器内存(Native VM memory):Unity脚本层相关的内存











[*]优缺点
[*]优点
[*]有助于防止内存泄漏
[*]保护内存访问










[*]缺点
[*]会影响运行时性能,因为分配托管内存对CPU来说非常耗时
[*]GC也可能会阻止CPU做其他工作,直到它完成











[*]C#非托管内存(C# unmanaged memory)
[*]Unity Collections namespace and package,no use of GC
[*]允许访问原生内存以微调内存分配











[*]原生内存(Native memory)
[*]用来运行引擎的C++内存
[*]Unity引擎的内部C/ c++核心拥有自己的内存管理系统,即原生内存
[*]通过C#的API可以间接访问原生内存











[*]非托管内存和原生内存有什么区别?是指的同一种内存吗?









[*]Editor和Runtime模式:管理方式、内存大小、分配时间和分配方式都不同
[*]按照管理方式

[*]引擎管理内存

[*]即引擎运行时需要分配的内存,开发者一般触碰不到









[*]用户管理内存

[*]开发时使用的内存








[*]Unity无法管理的内存

[*]用户分配的Native内存:比如自己写的C++ Native插件,因为Unity无法分析已编译的C++是如何分配和使用内存的
[*]Lua:由自己管理,Unity没法统计其内部情况


页: [1]
查看完整版本: Unity内存管理机制笔记--杨沁七