2026 年团结路演的收获
参加 "团结 "路演活动,并快速记录所学内容
内存基础知识
基本内容
-
我们使用虚拟内存 (Virtual Memory, 以下 VM)、不直接控制物理内存
-
VM 由页面构成,根据操作系统不同可能有差异
-
Window / Linux 4kb
-
iOS, macOS, Android 16kb
-
-
与页面对应的物理内存帧存在
-
根据内存压力等级,把虚拟内存合并到物理内存
Page Fault
当需要的页面没有加载在物理内存中时被称为 Page Fault
-
Clean Page: 原始数据未变化的页面,OS 可以随时释放,回收容易
-
Dirty Page: 应用程序新写入或修改后信息的页面,无法直接从内存退出
Memory Footprint
表达实际内存压力的指标,是把所有 dirty page 大小相加的结果
Memory Footprint != 物理内存使用量、常驻内存使用量
内存管理方法 (Windows vs Linux)
-
预约: 不使用物理内存、只分配虚拟内存
-
提交: 分配与实际虚拟内存等量的物理帧
-
取消提交: 保留预约空间的同时释放物理帧
Windows: 提供显式 API (virtual alloc, virtual free)
UNIX Like: 预约和提交没有显式分离
mmap 调用是隐式预约内存
直接访问页面时会发生延迟提交
没有显式 API、取消提交以提示形式工作
OS 决定何时回收页面
Unity GC 中的处理
Managed Heap
-
C# 内存分配全部在这里进行
-
由 Garbage Collector 管理
-
在 Unity Profiler 中显示为 GC.Alloc
Garbage Collector
-
定期从 Heap 中回收没有有效引用的 Object
-
执行 GC 也不会减少虚拟内存的总使用量
-
已回收的对象空间,从 OS 角度也是 Dirty
Segment
把连续的页面集合定义为 Segment
-
Managed Heap 使用的是 Segment、不是 Raw Page
-
一个 Segment 中可能分配多个 Object
-
把它分配后再释放,内部即使有未使用的状态,也会显示为 RAM 使用 (因为是 Dirty)
-
大多数情况完全没有问题、并且会很快被复用
-
太久空着的情况会浪费常驻内存,所以 Unity GC 间歇性地处理
Unity GC 所做的工作
-
使用 remap 战略
-
在 GC 多次执行之后,对 Old Empty Segment 执行 Drop、然后分配同样大小的 New Segment
-
Windows Remap 战略: 不释放 Segment、仅 Decommit (因为可以显式处理)
-
UNIX Like Remap 战略: 通过 munmap & mmap 实现 Remap (Hot Page 效果使缓存命中率高)
-
通过这种方式可以减少常驻内存的使用,是优点
不应反复执行 GC.Collect 的理由
-
不要强行执行 segment release
-
Collect 的开销大、可能导致应用冻结
-
它本身的意图是 decommit 长时间空闲的 segment
-
短暂空着的 segment decommit 与意图不符
-
强制 decommit 不会带来性能改善、只会产生 CPU 成本
- 反正下一帧就会被复用的 segment 也会被 decommit
Managed Heap 的 VM 使用量只增不减
-
Heap 只会增大、不会缩小
-
Boehm GC 本来的特性
-
没有理由 Trim 虚拟内存的分配量
-
大的虚拟内存 != 大的物理内存使用量
-
Segment 反正会被复用
-
游戏对象每帧都频繁创建和销毁
-
每次对象销毁就缩小 segment、只会浪费 CPU 性能
-
Unity Boehm GC
-
Unity 的 Incremental Boehm GC 是保守 GC (Conservative GC)
-
这是因为 Unity 提供了用于把过去写的 C# 代码转换为 C++ 的 IL2CPP,从那时起开始主要被使用
- 通过 IL2CPP 可以支持各种环境,但与 C# 不同,内存 GC 不会自动化,所以采用了开源 GC Boehm GC 看起来如此
-
以稳定性为中心而不是性能,把所有看起来像指针的值都视为引用
-
在 GC 期间不改变对象的位置 -> 没有 Compaction、容易出现内存碎片
关于 Compaction
有利于解决内存碎片的内存压缩工作。把所有未使用的空间清空并重新排列
由于需要精确的引用信息、保守的 GC 中无法实现
移动对象的位置会产生成本 (Stop The World + 实际移动)
CoreCLR GC
CoreCLR GC 是 Precise GC、是准确知道内存栈和寄存器中哪些值是引用的方式的 GC
而 .NET 把 CoreCLR 作为核心引擎使用 (CLR: Common Language Runtime)
CoreCLR 支持的内容
CoreCLR GC
JIT 编译器
Excecption Control
misc...
该 CoreCLR GC (以下 .NET GC) 使用基于代际的内存管理方式
-
把 Heap 分为 3 代来管理
-
Gen 0: 主要包含刚创建的新对象、频繁且快速的 GC 在这里发生 (大多数对象都在这里消亡)
-
Gen 1: 在 Gen 0 中存活下来的对象转移到这里、起 Buffer 作用
-
Gen 2: 长期存活的较重的对象所在的位置、GC 整理这里的成本相对较大
-
LOH: 非常大的对象会放到这个空间
在晋升过程中处理 memory Compaction、可以减少碎片化
LOH 由于 STW 较长,过去不进行 Compaction、但现在以显式选项化方式进行处理
转换的价值?
基本上 Unity 转向 .NET 时发生的副作用是 GC 改变、并且强调 GC 不是主要内容。但寻找其价值的话…
-
支持 Compaction、可以减少内存碎片化
-
可以让 GC 成本变得可预测
댓글 작성
게시글에 대한 의견을 남겨 주세요.