Unity DOTS学习记录(四)-内存模型

Chunk

在DOTS中,ECS会将实体数据组织在固定大小的内存块中,每个内存块大小为16KB,称为一个Chunk。Chunk是DOTS内存管理和调度的基本单元。

一个Chunk的整体结构如下:

1
2
3
4
5
┌──────────────────────────────┐
│ Chunk Header │ ← 元数据
├──────────────────────────────┤
│ Component Data (SoA) │ ← 实体数据
└──────────────────────────────┘

Chunk Header(组件数据)

Chunk Header中存放的是描述这块Chunk如何被ECS管理和解释的元数据,主要包括:

  • Archetype(架构):组件类型,组件大小,每个组件数组在Chunk中的偏移(*)
  • EntityCount:实体数量(int)
  • ChangeVersion:每个组件的修改版本(uint[])

Chunk Header的大小通常在几百字节以内,占整个Chunk的比例很小,绝大多数空间用于存放组件数据。

Component Data

1
2
3
4
public struct RotationSpeed : IComponentData
{
public float RadiansPerSecond;
}

以组件RotationSpeed为例,包含这个组件的Entity在Chunk中的实际内存布局如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
Chunk (16 KB)
┌─────────────────────────────────┐
│ Chunk Header │
│ - Archetype │
│ - EntityCount │
│ - ChangeVersion │
├─────────────────────────────────┤
│ LocalTransform[] │
│ A[0] A[1] A[2] ... │
├─────────────────────────────────┤
│ RotationSpeed[] │
│ B[0] B[1] B[2] ... │
└─────────────────────────────────┘

高效的数据访问方式

1
2
foreach (var (transform, speed) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>())

在执行这个遍历时,ECS底层其实是将每个Chunk中的组件进行遍历:

1
2
3
4
for each Chunk:
for i in 0..EntityCount:
transform = LocalTransform[i]
speed = RotationSpeed[i]

这种连续存储的的方式可以提高Cache命中率,在连续访问中便于CPU预取。

JobEntity 与 Chunk 并行

当使用IJobEntityScheduleParallel()时:

  • 不同Chunk之间可以并行处理
  • 每个Job通常负责一个或多个Chunk
  • 无需显式加锁,ECS已在Chunk层面保证安全

这也是ECS能在大量实体场景下保持高性能的关键原因。

总结

Chunk的设计决定了:ECS适合“小而多”的实体,不适合“大而少”的实体;当单个Entity接近Chunk大小时,ECS的性能优势将大幅减弱。