⚙️
核心原则:Native Container 不是“更快的 List”。它们用于非托管内存、Jobs 和 Burst;你必须显式管理容量、依赖和释放。

为什么需要 Native Container

普通数组、List 和 Dictionary 由 GC 管理,能保存对象引用,但 Burst Job 不能直接安全访问。Unity Collections 提供 Native Container:
  • 内存位于托管堆之外
  • 可被 Job Safety System 跟踪
  • 支持 Burst 编译
  • 通常只保存 unmanaged 数据
代价是需要显式 Dispose,错误的生命周期可能造成泄漏、越界或竞态。

常用容器

容器
用途
对应思路
NativeArray<T>
固定长度连续数据
数组
NativeList<T>
动态连续数据
List
NativeHashMap<K,V>
单写者键值映射
Dictionary
NativeParallelHashMap<K,V>
并行写入哈希表
并行 Dictionary
NativeHashSet<T>
唯一成员集合
HashSet
NativeQueue<T>
先进先出数据流
Queue
NativeReference<T>
单个共享值
可追踪引用

Allocator 决定生命周期

  • Temp:极短生命周期,不应跨帧或长期 Job。
  • TempJob:短期 Job 数据,需及时释放。
  • Persistent:跨多帧长期保存,开销较高但生命周期自由。
分配器不是性能标签,而是生命周期契约。选择比需要更短的分配器会触发安全错误;长期忘记释放 Persistent 会泄漏原生内存。

组件中持有 NativeList

若组件可能在 Job 未完成时禁用,必须先完成或串联保存的 JobHandle,再释放容器。

并行写入哈希表

调度:
ParallelWriter 只暴露适合并行的操作。容量不足时并行写入可能失败或产生昂贵扩容策略,因此应预估上限。

JobHandle 是所有权的一部分

容器的读写必须形成依赖图:
不要在 Producer 未完成时从主线程读取,也不要提前释放。临时容器可以使用 Dispose(dependency) 把释放动作安排到依赖之后。

ReadOnly 与并行限制

[ReadOnly] 告诉安全系统 Job 只读该容器,多个 Job 因此可以并行读取。写入同一索引或同一容器需要:
  • 正确 JobHandle 依赖
  • 支持并行写的容器和 Writer
  • 或明确划分每个 Job 的写入区间
关闭安全检查的 Player Build 不会替你修复竞态。Editor 能运行不代表并发逻辑正确。

容量、重用和 GC

Native Container 本身避免托管数据分配,但外围代码仍可能产生 GC:
  • Lambda 和闭包
  • LINQ
  • 每帧创建托管包装对象
  • Native 数据复制回新托管数组
高频系统应长期复用 Persistent 容器,在每帧开始时 Clear,并监控峰值容量。不要因为“零 GC”让容器无限增长。

NativeArray 与 NativeList 怎么选

  • Job 输出数量固定:NativeArray<T>
  • 输出数量动态但有上限:NativeList<T>
  • 多 Job 并行追加:使用容器提供的并行 Writer,并确认写入顺序不重要
  • 需要稳定顺序:预先计算索引或分段写入 Array
并行 Writer 通常不保证全局确定顺序。回放、联机确定性或测试依赖顺序时,需要显式排序或确定性索引。

FixedList 与 Blob

很小、长度上限固定、需要嵌入 struct 的数据可以使用 FixedList*。大量不可变共享数据在 Entities 工作流中可考虑 Blob Asset。
不要为了使用高级容器而强行把普通 MonoBehaviour 配置迁移到原生内存;跨边界复制也有成本。

常见错误

  • 忘记 Dispose
  • 释放时 Job 仍在使用容器。
  • 使用 Allocator.Temp 跨帧。
  • 容量不足却在并行写入。
  • 把托管对象或 string 放进要求 unmanaged 的容器。
  • 依赖 ParallelWriter 的插入顺序。
  • 每帧分配 Persistent 容器。
  • 只看 GC,不看原生内存和同步等待成本。

验证清单

  • 开启 Jobs Debugger / 安全检查进行开发测试。
  • Profiler 检查主线程等待 Job 的位置。
  • Memory Profiler 检查 Persistent 生命周期。
  • Burst Inspector 确认关键 Job 实际被 Burst 编译。
  • 用代表性数据量比较托管版本和 Native 版本。
  • 在 IL2CPP 目标平台验证,而不只在 Editor 测试。

系列导航

  1. 常用数据结构:Unity/C# 选择指南
  1. Array / List / NativeArray:连续内存容器
  1. Dictionary / HashSet / NativeHashMap:哈希容器
  1. Stack / Queue / Binary Heap:顺序与优先级
  1. 链表:Unity 中何时使用,何时避开
  1. Tree:层级、搜索与决策
  1. Graph:寻路、依赖与关系网络
  1. 空间索引:Grid / Spatial Hash / Quadtree / Octree
  1. Unity Native Containers:Jobs / Burst 下的数据结构

参考资料