1.ECS现在只剩下纯粹ECS(面向数据编程,数据在内存中更紧凑,更容易遍历,Cache命中更高)(还提供动画渲染等解决方案)

2.JobSystem多线程处理复杂计算

3.Burst编译成最合适目标平台的code

三相之力

大佬们来分析分析实战的优缺点

有哪些已经使用了DOTS技术的团队也可以随便说说


最近 DOTS 玩的比较多, 也来凑合几句. 首先针对高赞回答中的一些言论正一下试听:

DOTS 是不是专注解决性能问题?

是也不是, 准确来说, 能解决性能问题的技术是 JobSystem 和 Burst, ECS 只是专注于 Unity 应用长期以来缺乏的架构问题, 只不过 ECS 架构所特有的 Data-Orient 特性能更容易地解决 JobSystem 和 Burst 所需良好结构数据的问题.

能接近 Free Performance 的技术是 Burst, 唯一需要做的就是使用新的 Unity.Mathematics 来编写程序, 当然目前也有不少的限制(比如必须要blittable数据, 不支持 Class等), 同时也会有许多莫名其妙的 Bug (我甚至遇到过运行时内存数据被污染), 因此建议在开发阶段可以完全把 Burst 关掉.

Unity 目前在ECS上的动作是希望利用 ECS 的编译器将 JobSystem 也变为 Free Performance, 比如下面的代码就已经是 Jobify 过的代码, 不过用户可以几乎无感:

public class MySystem : SystemBase
{
protected override void OnUpdate()
{
Dependency = Entities
.ForEach((Entity entity, int entityInQueryIndex, ref Data data) =&> {
// my logic
}).Schedule(Dependency);
}
}

对比一下没有 Jobify 的写法:

public class MySystem : SystemBase
{
protected override void OnUpdate()
{
Entities
.ForEach((Entity entity, int entityInQueryIndex, ref Data data) =&> {
...
}).Run();
}
}

与此同时, 使用 ECS 的另一个好处(或坏处)是, 你之前关于 Unity 专有的许多性能优化知识都没用了, 什么 Object Poolling, Instancing, Scene Streaming, Caching Components , GameObject.Find 等等都永远说再见了.

耦合度变高了,原本实习生或者策划能干的活全堆给程序员了?

恰恰相反, 因为面向数据设计的初衷就是为了解耦合, 你见过谁在设计资料库时出现"耦合度"问题吗?实际上 ECS 的编码过程就是设计数据的过程, 用好资料库的第一第二范式就能设计出相对良好的程序结构, ECS world 里的数据也可以完全和副作用说拜拜.

至于策划? 可以看看我翻译的基于 Game Object Conversion 和 SubScene 的 DOTS 开发工作流 你就能明白为何结论也是恰恰相反: DOTS 让两者的角色更加清晰分离, 策划你就别插手代码了. 什么? 你还是想改? Visual Scripting 了解一下.

TinyMode为啥和ECS绑定?

很简单, 因为 H5 运行的环境恰恰是性能低下的 Mobile 平台, 性能这东西就像钱, 钱多就能买多点, 钱少买的少点, 并不是说我需要渲染上百万个对象才需要性能, 而是到处都需要性能, 这也是为啥 Unity 的口号是 Performance By Default. 而不是 Performance By 高工资程序员.

C# 不如 C++ 高效?

毫无必要的争论, 说这话的人知道 unsafe 关键字吗? 事实上, 不是 Unity 不在业务层面用Cpp, 而是 Unity 压根就不想用C++, 因此把整个引擎都逐渐转向 C# 了, 原有 C++ 的 Runtime Core 会越来越小, 新出的 Package 都是 C# based 的, 为啥? 因为 C# 开发体验好, Burst 和 JobSystem 又可以直接榨干 CPU , 两全其美.

DOTS学起来太难了!

不不不, 一点都不难, 之所以你觉得难, 是因为你没有转变过来 Data-Orient Design 的思想, 满脑子都还是继承一个 Person 类得到 Woman类, 这完全是两种处理问题的思路, 脑子转不过弯来当然觉得难了.

既然 DOTS 这么牛逼, 为啥还没普及?

很简单, DOTS 现阶段在完成度上就是接近废品的残次品, 我在这里说到过现阶段的DOTS, 粘过来给你们看看.

非常遗憾, 经过了 Unity 长达近三年的宣传, DOTS到今天完成度依然极低, 基本上处于不可用的状态, 一款游戏引擎必不可少的部分可简单列为下面八个, 接下来我分开说说在 DOTS 技术栈里他们的现状

  • Physics - 3分 物理自然是大部分游戏都不可或缺的部分, DOTS配套的 Unity Physics 依然处于非常早期的状态, Authoring 工具几乎为零, 甚至 Joint 或者 CharacterConroller 都要从 Sample 里找代码自己挨个儿实现, 由于它提供了一套船新的 API, 开发的过程只能用极难使用来形容, 来看看Trigger 事件的代码就知道了, 而在使用过程中也是Bug层出, 经常会遇到类似 Mesh Collider Bake错误, 而与之同期宣传的 Havok Physics for Unity 更是奇妙, 在长达半年的时间里, 只是单纯引入 Havok 的便会导致 HDRP 的渲染出问题. 而更进阶的布料, 粒子, 地形等也处于几乎为零的状态.
  • Graphics - 3分 DOTS与之配套的是图形系统是 HybridRenderer, 不过目前也只支持 Mesh Renderer, 什么 Particle 啦, Trail 啦, VFX都还没影儿, 更可惜的是官方现在优先关注于 HDRP 的兼容性(虽然也不怎么样), URP或者Built-In Pipeline就基本别想了, 遇到了问题也只能双手一摊.
  • Audio - 0分 Unity目前只开发了底层的 DSP 系统, 上层的 DOTS audio 说了一两年了连影子都没有, 完成度接近于零.
  • Animation - 0分 Unity Animation 依然处于极早期的开发中, DOTS Sample 展示了一些最最基本的使用, 类似 Animator 这样的高层方案现在还没影儿.
  • Network - 2分 也是 Unity 吹了两年的东西, DOTS Sample 中用的 NetCode 只不过把 FPSSample 中的部分代码挪过去了, 目前版本号 0.0.4, 意思就是太早期了别用.
  • UI - 0分 IMGUI, UI Element 和 Unity UI 三个Unity的产品均不支持 DOTS
  • AI - 0分 navmesh, ml-agent, ai planner 三个Unity的产品均不支持 DOTS
  • Input - 2分 老的 Input System 相对简单与DOTS没啥关系, 可新的 Input System 居然也不支持 DOTS, 幸运的是 InputSystem 相对独立, 嫁接进DOTS并不困难.

所以, 要怪就怪 Unity 的营销部门, 吹太过了, 步子太大扯到了蛋, 整个社区里弥漫著这样的气氛:

DOTS Sample 不是看起来挺好的啊

是的, 看起来是挺好的, 总的来讲 DOTS 肯定是未来, 现在是算是学习 DOTS 的不错时间, 最近正在写一个关于 DOTS Sample 的系列文章, 我会尽我所能将 DOTS Sample 所折射的 DOTS 的现阶段的优缺点和开发过程都理出来, 有兴趣的同学可以关注一下.

林七千:深入了解 Unity DOTS Sample (序章)?

zhuanlan.zhihu.com图标


DOTS里,除了ECS我都挺喜欢的(

不知道是不是没理解全面,仅从官方安利来看,我总觉得ECS把代码的耦合度变高了,维护和调试体验很差,原本实习生或者策划能干的活全堆给程序员了。

可能在我的理解中,ECS只需要用来写游戏中特别耗费性能的部分就好了,其他情况下还用原来的那一套就挺好的,把游戏全局甚至UI都强行上ECS简直有点灾难。

从最近的更新看来,unity确实可以这么干了,可以在gameobject的项目里插入部分ECS的东西,不过感觉两者之间的互通体验还有待提高。

最后,比较不理解的是为什么要把TinyMode和ECS强行绑定。讲真,我都tm上h5了,你觉得我还在乎性能吗?ecs在浏览器里又能提升多少性能呢。反而本来在egret上四五千块钱一个月的程序能干的活,在tiny上的工资成本直线上升


吐槽之外,对于DOTS技术本身,它很厉害。

Job System和Burst我们项目中有用上了,效果显著。

更重要的是,DOTS扩宽了Unity的可能性,你可以用它做一些简单的小东西,甚至游戏行业之外的东西,也可以用它做很多以前它难以胜任的大作。

对于Unity来说是好事。


其实我个人还是挺喜欢他们这种拚命压榨C#性能也不在业务层面用Cpp的折腾精神的。

并且挺愿意参与其中的。


DOTS是Unity因为宣传需要起的一个名字。它所包含的这三种技术中,与开发者关系最密切的是其中的ECS模式。这一技术应该会在这两年受到很大关注,也会有很快的发展。

先说一说我的观点,一些看法会随著DOTS的发展而修正。

1、Unity对多线程的支持正在改善,但传统的脚本写法是很大的障碍

经测试,Unity的JobSystem已经在2018之后的版本中默认启用,大部分引擎内部的功能已经可以被多核CPU并行执行,观察CPU占用率效果很明显。

但是脚本执行对时序要求高,如果不使用ECS,还用传统写法,脚本运行无法并行化。

针对ECS的性能,咱们的专栏《游戏开发入门指南——Unity+》中的一篇文章已经做了简单测试:(链接:ProcessCA:Unity 实体组件系统(ECS)——性能测试)

测试采用一个吃豆小游戏,分别写了MonoBehaviour和ECS两种版本

在敌人数量在17000左右时,游戏帧数掉到了30帧。

首先来看一下基于Monobehavior的实现。由于没有使用Unity的物理系统,所以基本上是脚本跟渲染两大块占用CPU的性能。

在主线程中一帧的时间已经超过30毫秒了,其中脚本执行就占用了几乎27毫秒。

我们可以看到Job System上的线程也帮我们分担了不少渲染上的负担:

值得一提的是在unity2017之后加入了Job System,所以Unity的渲染也会被分配到不同的线程中去执行,在一定程度上提高了整体运行效率。(也就是说,即便使用老的脚本系统,也能享受到JobSystem的一部分好处)

在使用了ECS化的脚本以后,并行化的脚本分配到了不同的工作线程上,充分利用了多核的性能。

在不同Worker Thread中有蓝色的代码执行片段

最后测试了ECS系统的极限,在敌人数量超过65000个时帧数下降到30帧。几乎是Mono的4倍(可能是因为测试机处理器有4个核心)。

2、DOTS是什么?

DOTS是给几项优化技术打包起了个名字,主要还是为了宣传考虑。

它主要包含3个技术:

  1. ECS模式。这种编程模式,要求游戏逻辑和引擎功能,都要按照 实体+组件+系统 的框架构建。采用ECS模式后,可以得到性能与多线程并行的优化。
  2. JobSystem。一套先进的多线程任务管理系统,搭配ECS时可以获得最佳性能。不搭配ECS也能在引擎的基本功能方面享受到好处。
  3. Burst,可以代替.Net原来的运行时环境(Mono或IL2CPP),用全新的编译器编译为原生代码,得到比IL2CPP更快的执行速度。当然代码最好能配合Burst做一些代码上的改动,以得到充分优化、避免BUG :)

DOTS系统成熟以后,游戏的脚本逻辑部分会和现有的组件式系统完全不同。原有脚本写法和ECS写法的区别,比两种语言的差别要大得多。所以起个好名字逐步发展新体系也是必要的。

3、ECS是什么?

首先ECS并非一种全新的技术,也不是Unity首先提出的。

这种技术的出现非常早,而近几年突然火爆,是因为暴雪的《守望先锋》。《守望先锋》的伺服器和客户端框架完全基于ECS构建,在游戏机制、网路、渲染方面都有非常出色的表现,这种技术上的巨大成功,必然会引领一波ECS的潮流。

这篇GDC分享3年前火了一阵

ECS不属于某种「设计模式」,之前我们常说的「设计模式」是在面向对象的框架之下讨论的,而ECS完全是另一种面向对象的编程方法,它对传统编程方法的颠覆性比Actor模式更强。至少目前来看,ECS仅适合于游戏开发领域。

  1. E代表实体(Entity)。实体在逻辑意义上是组件的容器,每个实体有若干个组件;但在实现上它只需要一个整数ID,区分不同的实体。
  2. C代表组件(Component),它不是目前Unity中的组件,而是一种只拥有数据,不能具有任何方法的结构体。
  3. S代表系统(System),和C相反,系统只能具有方法而不具有任何数据。

系统是程序逻辑的载体,游戏中有很多System会反复执行。每个System执行时它不管什么Entity,而只做两个步骤:

  1. 从所有的组件中筛选合适的「组件组合」。例如某个移动system,只关心同时具有「移动组件」和「旋转组件」的对象。
  2. 对筛选出的组件进行某种操作。例如上面的移动system,会修改移动组件中保存的位置变数,再修改旋转组件中保存的朝向变数。

一个游戏中,大量的Component每一帧被很多System修改,就形成了游戏逻辑。

那为什么说ECS非常厉害呢?关键是:

  1. 每个system所做的事情都非常简单,非常容易并行执行。而且只要标记出哪些数据是只读的、哪些是可写的,就能让ECS系统进一步优化。现代手机和PC核心数都挺多,可以达到恐怖的并行效率。
  2. 由于组件C只是单纯的数据,大量组件数据在内存中很容易按顺序排好位置,系统处理这些整整齐齐的数据极为高效。而且,这种内存布局可以提高CPU缓存命中率,现代CPU动辄有十几兆、几十兆的缓存,在缓存中执行比火箭还快。

4、ECS目前的发展阻力是什么?

通过前面的介绍可以看出,ECS可不是什么「易学易用」的新技术,它对现有的开发模式,甚至现有的Unity编辑器来说都是颠覆性的。

不仅如此,ECS对初学者来说有著巨大障碍:除了游戏技术圈,人们很难在任何其它地方学到ECS编程方法

无论你在什么地方学习编程,能搞清楚传统的面向对象已经很不错了,ECS只能在你有一定编程基础后,通过网路资料实践自学。这一问题在ECS的推广方面是致命的。

因为ECS这一技术本身还是比较新颖的,高手们都还没有把ECS系统摸透,没有系统化,更别提能够在不误人子弟的前提下教会新人。

但是话说回来,ECS虽然是一种全新的编程思想,但对于一些专业开发者来说并不难,比如咱们专栏里已经有写过一些实践分享:

ProcessCA:Unity 实体组件系统(ECS)——性能测试

zhazha chen:Unity ECS 高性能探索

对于专业开发者来说,目前在Unity中实践ECS,阻力来自于:Unity官方对于DOTS依然是缓慢开发中,还远远谈不上成熟

例如,由于ECS的游戏对象不再兼容原来的GameObject,导致ECS物体在场景编辑器中看不到。如何解决官方给出了至少两种方案:加个代理适配,或是彻底换成新编辑器窗口(比如Entity Debugger之类的工具)。但是这些方案都还不完善…… :)

还有ECS在渲染方便和原系统存在兼容问题,雨松Momo的ECS分享视频中,用了较多篇幅解释在DOTS技术栈下如何妥善渲染。可以看出问题还很多,针对不同项目要考虑不同的方法。

5、我们应该如何看待DOTS?

重点是,虽然Unity的DOTS技术存在巨多问题,无论Unity ECS还是Burst,说起来都是各种吐槽。但作为专业的软体工程师,应当以最大程度的积极性拥抱这些新技术

纵观历史,新技术出现时总是带著一身毛病——还没马车跑得快的汽车,还没蒸汽机好用的内燃机,还没滑膛枪可靠的线膛枪,感觉随时会核爆的核电站。一切当今习以为常的东西,在当年都被无情地嘲笑过。

就算发展慢、就算发展中出现下滑和反复。ECS技术在内存布局、缓存友好性、多线程友好性等方面达到了传统编程模式望尘莫及的高度。甚至可以说,很常规的ECS代码就已经能达到某些深度优化过的传统代码的性能。

所以从发展趋势看,我们没有理由故步自封、拒绝这一新技术。

PS:话说回来,现在学DOTS并不是现在就要用到商业项目里。在商业项目中,采用这种尚不成熟的新技术一定要得到团队的一致同意和Leader的支持,否则就是非常不负责任的决定。 :)


泻药,个人观点有3条:

  1. ECS是好东西,Unity的ECS不是。
  2. Job System是好东西,Unity的Job System也不错,但还有一定的进步空间。
  3. Burst目前看来提升确实很大,但是没有跟手写SIMD比较过。

ECS的思想其实从十几年前机能严重不足的时候,到现在都一直在影响游戏开发行业,无论是里边有过程式编程的影子,还是函数式编程的影子,亦或是针对缓存和CPU密集运算的优化思想,都是在切实的解决游戏逻辑和渲染中的痛点,而且即使不是纯ECS,许多团队的框架也在向这个方向靠拢。Unity的ECS一方面完成度太低,之前看到版本号还是0.x,就比较尴尬,另外从我个人自私的角度分析(本人是个写渲染的)就是没大有和渲染层接壤,比如VLK和DX12这种现代的API是可以直接并行产生PSO,并行准备CommandBuffer(CommandList) 等等,在这些方面Unity只提供了SRP这种很上层的封装,很难将Job System和ECS灵活的应用上去。

Job System这种线程调度方式,应该是同步多线程中速度最快的方法,因为队列数量提前可知所以几乎不存在高消耗的锁和等待,同时一次性触发不会涉及到上下文,线程启动消耗等。但是官方的Demo好像有涉及到一些非同步多线程,个人认为Job System这种一次性触发的设计模式并不适合非同步多线程,平时我在开发时也经常会用到Job System这种多线程思想,对性能的提升大有裨益。

之前有人问Job System,Unreal的Task Graph有什么关系和区别,看起来Task Graph在设计时更多的考虑了非同步能力,而不是这么注重一口气憋住的同步能力,而且Unreal官方似乎推荐使用Task作为延时的逻辑触发,而Unity推荐使用协程做等待,让其他线程做纯运算,在设计理念上应该存在一些不同,本人在此不置褒贬。

Burst据说是处理SIMD的效果很好,经过一些测试发现效果也确实不错,但是平常这种优化手写也是能做到的,所以究竟和平常手写的带_m128这种的数学库,比如DX提供的DirectX::XMVECTOR DirectX::XMMATRIX有多少提升,这一点还有待探索,这里持保留态度。


最近用了下坑太多了。这东西目前给我感觉就是宣传能100%解决问题A,实际上能轻松解决10%的问题A,深入进去解决40%,开洞解决80%。但是因为宣传给的例子全选的都是在那10%到40%的情况,让你以为可以轻松解决100%的问题。

1. 一个问题是大量实现是基于非语法层面的命名做的trick,考虑到c#和unity一贯如此倒也能接受,但是很多trick不放文档上跟你讲清楚,就很坑。举个例子,自己写个NativeContainer,一定要有一个AtomicSafetyHandle和一个DisposeSentinel。然而你光有这两个field还不行,必须强制命名为m_Safety和m_DisposeSentinel。NativeContainerAttribute的doc里并没有讲这一点,翻了forum才发现这个问题。

2. 大量功能性api不完全/只能在主线程跑,你想job化都不行。举个例子,VisibleLight这个struct的设计,颜色位置啥的信息都有,就是没有阴影信息,所以必须得调VisibleLight.light去拿原来那个light,然而这是一个managed call,所以Job里用不了,你说dt不dt。

3. 接1,各种奇怪的判定方法。IJobParallelFor里会把任意两个type一致的打了[NativeDisableParallelForRestriction]的NativeContainer认为是两个可能会混淆的NativeArray。且不说这两个东西也不一定是NativeArray,哪怕这两个完全内容不一样它也不管。我就纳闷,我都取消并行的检查限制了,全都自己管理了,这两NativeContainer会不会混淆我不比你unity更有b数?

所以说这玩意儿就是为「理想问题」而设的。写个什么千万个小行星绕太阳转没问题,可我要实现这玩意干啥?


推荐阅读:
相关文章