Unity新推出的DOTS技术如何?
Unity新推出的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的产品均不支持 DOTSAI - 0分 navmesh, ml-agent, ai planner 三个Unity的产品均不支持 DOTSInput - 2分 老的 Input System 相对简单与DOTS没啥关系, 可新的 Input System 居然也不支持 DOTS, 幸运的是 InputSystem 相对独立, 嫁接进DOTS并不困难.
所以, 要怪就怪 Unity 的营销部门, 吹太过了, 步子太大扯到了蛋, 整个社区里弥漫着这样的气氛:
DOTS Sample 不是看起来挺好的啊
是的, 看起来是挺好的, 总的来讲 DOTS 肯定是未来, 现在是算是学习 DOTS 的不错时间, 最近正在写一个关于 DOTS Sample 的系列文章, 我会尽我所能将 DOTS Sample 所折射的 DOTS 的现阶段的优缺点和开发过程都理出来, 有兴趣的同学可以关注一下.
林七千:深入了解 Unity DOTS Sample (序章) 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个技术:
ECS模式。这种编程模式,要求游戏逻辑和引擎功能,都要按照 实体+组件+系统 的框架构建。采用ECS模式后,可以得到性能与多线程并行的优化。JobSystem。一套先进的多线程任务管理系统,搭配ECS时可以获得最佳性能。不搭配ECS也能在引擎的基本功能方面享受到好处。Burst,可以代替.Net原来的运行时环境(Mono或IL2CPP),用全新的编译器编译为原生代码,得到比IL2CPP更快的执行速度。当然代码最好能配合Burst做一些代码上的改动,以得到充分优化、避免BUG:)
DOTS系统成熟以后,游戏的脚本逻辑部分会和现有的组件式系统完全不同。原有脚本写法和ECS写法的区别,比两种语言的差别要大得多。所以起个好名字逐步发展新体系也是必要的。
3、ECS是什么?
首先ECS并非一种全新的技术,也不是Unity首先提出的。
这种技术的出现非常早,而近几年突然火爆,是因为暴雪的《守望先锋》。《守望先锋》的服务器和客户端框架完全基于ECS构建,在游戏机制、网络、渲染方面都有非常出色的表现,这种技术上的巨大成功,必然会引领一波ECS的潮流。
这篇GDC分享3年前火了一阵
ECS不属于某种“设计模式”,之前我们常说的“设计模式”是在面向对象的框架之下讨论的,而ECS完全是另一种面向对象的编程方法,它对传统编程方法的颠覆性比Actor模式更强。至少目前来看,ECS仅适合于游戏开发领域。
E代表实体(Entity)。实体在逻辑意义上是组件的容器,每个实体有若干个组件;但在实现上它只需要一个整数ID,区分不同的实体。C代表组件(Component),它不是目前Unity中的组件,而是一种只拥有数据,不能具有任何方法的结构体。S代表系统(System),和C相反,系统只能具有方法而不具有任何数据。
系统是程序逻辑的载体,游戏中有很多System会反复执行。每个System执行时它不管什么Entity,而只做两个步骤:
从所有的组件中筛选合适的“组件组合”。例如某个移动system,只关心同时具有“移动组件”和“旋转组件”的对象。对筛选出的组件进行某种操作。例如上面的移动system,会修改移动组件中保存的位置变量,再修改旋转组件中保存的朝向变量。
一个游戏中,大量的Component每一帧被很多System修改,就形成了游戏逻辑。
那为什么说ECS非常厉害呢?关键是:
每个system所做的事情都非常简单,非常容易并行执行。而且只要标记出哪些数据是只读的、哪些是可写的,就能让ECS系统进一步优化。现代手机和PC核心数都挺多,可以达到恐怖的并行效率。由于组件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的支持,否则就是非常不负责任的决定。:) 最近用了下坑太多了。这东西目前给我感觉就是宣传能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一致的打了的NativeContainer认为是两个可能会混淆的NativeArray。且不说这两个东西也不一定是NativeArray,哪怕这两个完全内容不一样它也不管。我就纳闷,我都取消并行的检查限制了,全都自己管理了,这两NativeContainer会不会混淆我不比你unity更有b数?
所以说这玩意儿就是为“理想问题”而设的。写个什么千万个小行星绕太阳转没问题,可我要实现这玩意干啥? 泻药,个人观点有3条:
ECS是好东西,Unity的ECS不是。Job System是好东西,Unity的Job System也不错,但还有一定的进步空间。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有多少提升,这一点还有待探索,这里持保留态度。 刚刚好最近几天研究了一下Unity2019.3下的DOTS技术栈。
只能说理想很丰满,现实很骨感!
本来是想把频繁变化的HUD部分剥离到ECS实现来优化一下性能,结果发现ECS提出来快3年了,版本还停留在0.4.0-preview版本,接口很不稳定,对UGUI的支持也寥寥可数,根本没到可以使用的地步!要用就要把底层渲染完全实现一遍,工作量太大了。
官方的ECSSamples真的是Sample,完全看不出复杂一点的系统交互要怎么做比较好。
总的来说,感觉东西是不错,但是离项目使用还有一段距离!
升级到了最新的2019.3,DOTS还是不支持特效,DOTSSample里有动画和新版VFX的例子,但是还不到到实际工程中使用的程度!保守估计DOTS完善还需要1年。 泻药,比较不实战,有点理论党,还是答下。
unity那个ecs我粗浅抄了一遍,设计上有种本末倒置的感觉,但是纯论使用的话暴露出来的用法是一样的还算可以。单论unity ecs的话,性能可能并不会有想象那么好。(官方那些案例都是很少的组合,chunk几乎就那么几种,上了instance看起来很快很厉害)
基本是给job sys做嫁衣的。但是也带来一些开发思想的转变,这个也比较重要。不像oop有设计模式这样一套东西,ecs怎么写可能要项目里去积累具体经验。
当然看了某些烂到极致的项目后觉得ecs还是不错的…至少它自打一开始就保持了分离性,管理上比较亲和。
并且现在看的话也是浅浅嵌进引擎的,毕竟unity本身还是传统设计不是ecs。也不太适合和传统mono一起工作。
job system就是拿来解决多线程同步的,具体原理嘛就是它把作业分派给活动线程并且自动为你维持作业保序性,配合ecs可以很轻易实现多线程工作。要是说三相之力的话这个是大头。
看了题主的补充后才知道,ecs更新了,并且方案完备了。去试一下,回来再补充体验。 目前项目在大量使用ecs
通过goconvert可以很方便地将prefab转换成entity,项目中除了需要交互的物件都通过ecs来加载,要在ecs上写逻辑的话还是需要不少思维转换的,目前测下来
1,省去了实例化prefab的开销
2,主线程,渲染线程相比于go有很多ms的节省,instancing的打断比go少,dc能少一倍
3,内存没测出差距
4,物理暂时没有用ecs来做
总的来说,在花费不多精力的情况下可以很方便地提升场景静态物件的加载和渲染性能
花草等小型植被还是自己写instancing更快 ECS会让你以另一种方式思考,思路清晰了,适应需求变更的能力变强了,因为一开始就会以粒度非常细的思路来开发功能。
IComponentData里只能放blittable类型,所以一开始用matrix44代替了数组,后来照着int3写了个int16。
而且你很难再遇到NullException了,妈妈也不用担心你找不到对象了。
但是目前EntityDebugger还有些问题,还有多线程等其他原因让debug会有些困难。
JobSystem和Burst目前我的思路就是
尽一切可能让代码能并行。
尽一切可能让能标记在execute上。
多用math别用Mathf。
不可避免的要从job往monobehavior发送一些数据,我目前用的是nativequeue,它可以并行写。
动画/UI 等等表现层的东西主要还是在monobehavior上,官方的工具还没做好,没能ECS化。
物理用了ECS Unity Physics, 熟悉了用法之后也还不错。尝试了Havok,比unity physics还要快一倍,不过没用于产品。
由于Job里只能遍历一类EntityQuery,当处理两类EntityQuery之间的相互作用时就不得不在主线程拷贝某一份数据到job里,由于很多system都需要这些数据,所以就弄个系统专门每帧拷贝一次数据,免得各个system都在需要数据的时候拷贝一遍,结果大部分系统都看起来变快了,可是有一个系统却似乎因为使用这个数据而变慢了,于是这个系统就自己拷贝了,然后快了。咱也不懂,也不知道问谁,反正就对着profile一直试各种方法,那个快就用哪个。
不同的job可能会用到一些相同的工具类函数,比如检查地图某个位置是不是空的,曾尝试将工具类函数拿出来放到别的地方,结果工具类函数有一行PhysicsWorld.CastRay(input,out raycasthit),这个函数放到job外就会报错,放到job内就不会。咱还是不懂为啥,反正能运行就行。
页:
[1]
2