Unity DOTS
DOTS - Data Oriented Tech Stack - 面向数据的技术栈
前言
公司1024节日时,邀请人来介绍Unity DOTS,有点兴趣,自己也查阅了一番资料,简述一下我的理解。
背景
- 硬件发展瓶颈
- CPU发展,趋于平缓;后续发展由单核转向多核
- 内存发展,整体量级不足,无法满足CPU需求
- 缺少能充分榨干多核CPU的引擎框架
方案
- Cache的充分利用
- 减少碎片化内存
- 减少Cache Miss
- 减少数据关联性,多核批量处理数据(解耦关联,可以不加锁)
技术栈
- Entity Component System(ECS)
- Burst Compiler
- 现在主流CPU都有专用浮点运算单元(尤其做 向量运算),过去的通用的编译器无法充分利用这块
- C# Job System
- 面向多线程编程的方案
- 与ECS框架配合使用
- Native Containers
- 配合ECS、C# Job System使用
- ECS需要用大量数组、队列等容器,传统的容器被封装过,性能低
ECS相关
结构类型:
- Entity, 唯一ID, 指向所属Component位置, 作为类似指针但并不是指针
- Component, 保存对象的数据, 可当作struct, 不支持托管类型(例如引用)
- Archetype, N种Compoent的组合, 每个Archetype的内存是连续排布, Archetype的变化,是通过内存拷贝来进行(由于是纯拷贝,速度很快)
- Chunk, Component存储在Chunk中,多个Chunk隶属于同一个Archetype
- System/Job System, 处理数据的方法,Job System负责多线程处理
流程
- 每个Entity有位置和速度两个属性,分别由 Component Velocity 和 Component Position 代表;
- 在计算坐标的update中,所有 Component Velocity 与 Component Position 传递给 system 计算出坐标,从而更新 Component Position。
为什么性能高:
- 正常模式下,在更新坐标的update中,遍历所有对象,获取对象的坐标及位置,计算出最终坐标,再更新位置
- ECS中,获取所有速度及坐标组件,计算初最终坐标,更新坐标
- 内存存储中,正常模式的对象的对应字段不一定在一块,因此Cache Miss相对高一些
- 在DOTS中,提供内置容器及结构,进行更细致的内存管理;降低数据的耦合性后,可以执行多线程的操作
应用及发展
Physics
Hybrid Render(HDRP, URP)
Unity-ECS 渲染后端;相机culling、LOD计算,全用DOTS进行物理优化
未来 - GPU Driven渲染管线
我的理解:
对于当前的硬件趋势及问题的重要解决方案。
但是,要充分利用多核的无锁并行,就需要进行很大精力的数据解耦,这方面目前还是依赖开发者自行解决,因此,开发者的门槛其实很高。
所以,感觉并不是很适合在游戏开发内全部采用,更适合在一些关键模块中进行使用,也便于部分更专业的开发者维护。
笔记
以下是我在听介绍时的部分记录
解决性能问题,目标为真正的 AAA GAME
技术栈
- Entity Component System(ECS)
- Burst compiler(High Performance C# - HPC#)
- 编译器
- 现在主流CPU都有专用浮点运算单元(尤其做 向量运算),过去的通用的编译器无法充分利用这块
- C# Job System
- 面向多线程编程的方案
- 与ECS框架配合使用
- Native Containers
- 配合ECS、C# Job System使用
- ECS需要用大量数组、队列等容器,传统的容器被封装过,性能低
内存满足不了CPU
- CPU开始开辟很多Cache,但是Cache有限
- CPU从Cache读数据很快,没找到就产生一个Cache Miss,就要从RAM读取很慢
- CPU读取Cache,一次读一行,每行基本 64字节
目标
- 性能,充分利用多核
- 市面上还没有哪款引擎能充分利用多核(前提,硬件性能濒近极限)
- 高度模块化,选配
解决问题
- Cache Miss的优化
- 多线程的编程,批量处理数据,等比划分很多份,每个线程跑一部分(重点在于解耦,避免依赖性,因此访问内存可以不加锁)
Entity
- 唯一ID,指向位置
- Entity有数组,删除的时候,与最后一位进行交换(不会进行整体位移)
- 不要通过EntityManager遍历数组查找entity,性能差;通过query访问
Archetype
- n种component组合形式;每个archetype内存是连续排布,archetype发生变化,进行内存拷贝(仅仅拷贝,速度快,没有性能问题)
- archetype太复杂,每个chunk存的entity有限,会剩余部分空间用不上
Chunks
- 存Entity多个component,存储的性能问题
- 固定大小 16KB(性能优化的时候,要注意)
- 1个Archetype是chunk的数组
- 解决内存碎片化
System
- 处理数据,返回想要的内存数组
Job System
- 多线程处理数据
Queries
- 返回一组chunk,进行连续访问(先拿一个archetype,再筛想要的chunk返回)
Component
- 保存对象的数据,类似于C的struct,不支持托管类型(例如引用),必须是实型
- 希望每个component尽量小
Special Component
- Tag Component,标记,不占内存
- Shared Component,共享内存;Read Only;可以是托管类型(比如引用)
- Chunk Component,Chunk内共享内存,可读可写,不可是托管类型
- Class Component,让老结构更好转换到ECS框架;对引用类型数据存储;不推荐
- System State Component,destroy的时候不被清空
- 检测某个对象数据是否有改变,通过版本号是否变化
Dyncmic Buffers
- 动态数组,无限扩充,默认128字节
World
- 隔离,Entity ID可重复了
提供GameObject和ECS混用方式,编辑器用GameObject,会使用工具流自动转换为ECS
参考资料