中文
2026 年团结路演的收获

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 成本变得可预测

댓글 작성

게시글에 대한 의견을 남겨 주세요.

댓글 0