Unity 6.6默认启用mimalloc:原理与On/Off性能对比实测
Unity 6.6でデフォルトになったmimallocとは? 仕組みを理解してON/OFFを計測してみた
はじめに
- Unity 6.6では、ネイティブメモリのメインアロケータがmimallocにデフォルトで切り替わりました(6.5ではオプションでデフォルトOFF)
- スレッドごとにヒープを持つことで、マルチスレッドでの確保・解放の取り合いを減らす
- C#のGCとは無関係。ゲーム側のコード変更は基本的に不要
- iOSは対象外(もともとシステムのアロケータを使っている)
メモリアロケータとは
简单来说,
当程序请求“请给我内存”时,系统会准备并交付实际可用的内存。
例如在C/C++的世界中,
プログラム : 「100bytes欲しい」
↓
メモリーアロケーター : 「メモリ上のこの場所を使ってください」
↓
プログラム : 「100bytes GET」
发挥着这样的作用。
使用完毕后,
プログラム : 「100bytesもういらない」
↓
メモリーアロケーター : 「その領域を再利用できるようにしておきます」
也承担着相应的职责。
allocate(确保) 和 free(释放)
的管理是其主要职责。
而重要的一点是,在确保内存时,
并不是每次都要向OS提出请求
这一点。
例如,在程序执行过程中,
100 bytes欲しい
200 bytes欲しい
64 bytes欲しい
500 bytes欲しい
即使被要求,
如果每次都向OS
“请给我100 bytes”
这样请求的话,效率会非常低。
因此,内存分配器是,
OS
↓
アロケータ
┌──────────────────────────────┐
│ 大きなメモリ領域 │
└──────────────────────────────┘
↓
アロケータ
┌────┬────┬────┬────┬──────────┐
│100 │200 │ 64 │500 │ │
└────┴────┴────┴────┴──────────┘
像这样由自己进行细致管理。
因此,
内存分配器 = 高效地借出和回收内存的管理者
就是这样的定位。
与C# GC的区别
Unity中大致分为
「C#侧的内存」 和 「原生侧的内存」 。
| C#侧的内存 | C#托管内存 | GC回收 |
|---|---|---|
| 原生侧的内存 | 原生内存 | Unity 的原生分配器群进行管理(从 6.6 开始主要使用 mimalloc) |
〇 C# 侧
var list = new List<int>();
此类 C# 对象的内存基本上分配在 C# 的托管堆上。
当不再需要时,
GC(垃圾回收器)
会进行回收。
〇 原生端
另一方面,在Unity内部,
- Texture
- Mesh
- NativeArray等原生容器
- 与资产相关的内部数据
- 引擎内部的C++对象
- Job System等内部处理
- 各种原生数据
等等,与C#托管内存不同,会大量使用原生内存。
负责分配和释放这些内存的是本次
Native Memory Allocator
。
因此,
mimalloc = 替代C# GC的东西
并非如此。
DynamicHeapAllocator的问题
Unity 的传统 DynamicHeapAllocator 机制,据官方帖子说明,大致具有如下结构。
| 対象 | 机制 | 锁 |
|---|---|---|
| 小型分配(约64字节及以下) | Bucket Allocator | 无(无锁) |
| 从主线程分配 | 主线程专用的动态堆(TLSF) | 无 |
| 从其他线程分配 | 所有线程共享的动态堆(TLSF) | 有 |
〇 分配器争用
DynamicHeapAllocator 的机制是,
除主线程外的其他线程在共享堆上操作时需要获取锁。
当一个线程持有锁时,其他线程必须等待。
这种多个线程争夺同一分配器的状态被称为 allocator contention(分配器争用)。
在旧式游戏中,
Main Thread
↓
ほとんどの処理
这样的形式也能成立。
然而,当前 Unity 越来越多地采用多线程运行模式,导致出现
Main Thread
│
├── Job System
│ ├── Worker 1
│ ├── Worker 2
│ └── Worker 3
│
├── Render Thread
│
├── Async Loading
│
└── その他Native Thread
这样的情况,
这些情况会同时申请和释放原生内存。
尽管通过增加 CPU 核心实现了并行化,但在内存分配环节却出现了所有线程竞争同一个锁的现象
这一现象随之发生。
mimalloc / 每个线程的堆
〇 mimalloc这一机制
mimalloc是由微软开发的开源通用内存分配器。
Unity的官方帖子中将mimalloc介绍为“具有每线程堆(per-thread heap)和无锁高速路径的分配器”。
在常规的分配与释放操作中,线程之间不会争夺锁。
〇 什么是 per-thread heap?
顾名思义,per-thread = 为每个线程准备的堆。
Thread A
↓
┌─────────────┐
│ Heap A │
└─────────────┘
Thread B
↓
┌─────────────┐
│ Heap B │
└─────────────┘
Thread C
↓
┌─────────────┐
│ Heap C │
└─────────────┘
A在分配内存时,
不再需要“获取共享堆的锁”
而是可以
“从自己的堆中分配”
。
这减少了典型分配路径中线程之间发生冲突的机会。
mimalloc 本身也采用了使用线程局部堆和段来减少竞争的设计。
Unity 6.6 中设为默认
mimalloc 本身在 Unity 6.5 时就已经作为可选选项存在,但从 Unity 6.6 开始,该设置默认开启。

Project Settings > Memory Settings > Main Allocator > Mimalloc enabled
实际上的ON/OFF比较
以下是在Windows 11上,使用mimalloc开启与关闭的运行文件进行的比较。
計測環境
- Unity 6000.6.0f1
- Windows 11、Intel Core i7-8750H(6核12线程)、内存16GB
- Windows独立版本发布构建(Mono,Burst启用)
- 11个作业工作器+主线程
計測方法
在通过 Burst 编译的作业中,UnsafeUtility.Malloc / Free(Allocator.Persistent)被大量重复执行,并测量了所花费的时间。
每个场景在3次预热后进行了10次测量,取其中位数(median)。
尚未在 Android 真机上进行验证。
关闭时
开启时
尽管内容难以看清,但在所有测量中,启用 mimalloc 的处理速度都更快。
| 名前 | 正在做什么 |
|---|---|
| Small / Medium | 确保的内存大小。Small 为较小(16〜256字节),Medium 为中等(1〜16KB) |
| MainThread | 仅通过主线程大量进行内存分配与释放 |
| Parallel | 将相同量的处理分配到12个线程中并行执行 |
| Cross_WorkerAlloc_MainFree | 在工作线程中分配,在主线程中释放 |
| Cross_MainAlloc_WorkerFree | 在主线程中分配,在工作线程中释放 |
| alloc / free | 分配耗时 / 释放耗时 |
| 场景 | 关闭 | 开启 | 开启时的速度 |
|---|---|---|---|
| Churn_Small_MainThread | 16.735 毫秒 | 6.701 毫秒 | 約2.5倍 |
| Churn_Small_Parallel | 31.008 毫秒 | 1.363 毫秒 | 約22.7倍 |
| Churn_Medium_MainThread | 21.381 毫秒 | 14.026 毫秒 | 約1.5倍 |
| Churn_Medium_Parallel | 38.890 毫秒 | 2.408 毫秒 | 約16.2倍 |
| Cross_WorkerAlloc_MainFree(确保) | 11.381 毫秒 | 0.999 毫秒 | 約11.4倍 |
| Cross_WorkerAlloc_MainFree(释放) | 5.705 毫秒 | 2.631 毫秒 | 約2.2倍 |
| Cross_MainAlloc_WorkerFree(确保) | 6.975 毫秒 | 3.006 毫秒 | 約2.3倍 |
| Cross_MainAlloc_WorkerFree(释放) | 5.971 毫秒 | 0.839 毫秒 | 約7.1倍 |
从结果中可以得知
在 OFF 状态下,并行处理反而会变慢
将相同量的处理分配到 12 个线程中时,比单线程时慢了约 2 倍(即速度变为原来的 0.54 倍)。
Unity 的官方帖子也指出,当多个线程争用同一个锁时,增加核心数并不能解决问题,
相反,核心数越多情况反而越糟。
此外,还解释说等待锁的线程可能会被操作系统挂起,导致恢复所需的成本可能超过获取锁本身的成本。
在 ON 状态下,线程越多速度越快
在 ON 状态下,速度提高了约 5 到 6 倍。本次测试使用的 CPU 为物理 6 核,因此性能提升幅度接近核心数量。
主线程单独运行也变快了
Unity 的官方基准测试结果显示,在单线程和多线程模式下 mimalloc 都更快。
发布版本的测量
Unity 的官方基准测试为了观察最坏情况,使用的是 development + profiler 构建进行测量的。
本次我们使用发布版本进行了测量,并确认了相同的趋势。
※图片中的内存值(allocated/reserved)是 Profiler API 的值,并非物理内存的使用量,因此不作为比较对象。 Unity 的官方帖子中说明,通过调整 mimalloc 的设置,物理内存的使用量与以往相同或更少。
用于比较创建的脚本
https://gist.github.com/Matsuta211/1b3a001453531ef9fc83fbc337f23fce
最后
mimalloc 将从 Unity 6.6 开始默认启用,游戏侧无需更改代码。
ただし、効果の大きさはゲームによって異なります。
Job Systemや非同期ロードなど、ワーカースレッドでネイティブメモリを多く確保するゲームほど恩恵が大きく、
メインスレッド中心のゲームでは、体感できる差は小さいかもしれません。
一方で、これまで懸念だった「ワーカースレッドでの確保は、ロックの取り合いで遅くなる」というコストは大きく下がりました。
Job Systemを使った並列化が、より選びやすくなったと言えます。
※Unityをアップグレードした既存プロジェクトで、この設定がONになるかどうかは未確認です。
Project Settings > Memory Settings で確認してください。
参考
来源:Qiita · Unity 开发社区 · qiita.com

