当前位置:   article > 正文

[Unity3D] 对《CECC | Unity DOTS:面向数据编程的技术栈》报告的摘抄学习_unity2023 dots

unity2023 dots

在第四届中国计算机教育大会(Computer Education Conference of China)上,Unity 中国技术总监张黎明先生分享了 Unity DOTS 的基本概念、应用原理、1.0 版本的最新进展以及未来的开发计划。以下内容就张黎明先生的分享,进行学习总结。

原报告链接:https://developer.unity.cn/projects/64538043edbc2ac09cb512fa

DOTS 的发展背景

Unity 今年(2023)刚刚发布 DOTS 的 1.0 版本,从这个维度上来说,DOTS 是一个非常新的技术。不过,其实 Unity 已经在 2018 年发布过 DOTS 最早的一个 demo 和当时的技术展示,从这个维度上来说,这个技术已经演进了五年的时间(2018 - 2023)。可以肯定的是,DOTS 会是 Unity 未来的一个发展方向,尤其是面向高性能的计算领域提供 Unity 的解决方案。

为什么使用 DOTS

Megacity

Unity 在 2018 年发布了一个使用了 DOTS 技术的 demo,名为 Megacity。在这个 demo 中,有一个非常庞大的城市,里面有几百万个静态的 3D 的模型以及上万个音源,有数千个在空中飞行的汽车,它的计算量非常庞大。

张黎明先生提到,在有 DOTS 技术之前,想实时做这种大规模的仿真或超复杂场景的渲染是非常困难的(后面会讲到为什么使用 DOTS 技术可以做到这种水平的仿真以及渲染)。过去五年时间,Unity 对 DOTS 经过了内部几代的演进,这个 demo 也有了最新的升级版,它是一个开源的工程,并可以在 github Unity 仓库中找到。

DOTS 实例2
另一个使用了 DOTS 技术的例子是某游戏公司开发的一款游戏(这款游戏已经上线了)。它的特点是同场景中做了海量的 3D 僵尸角色,也是使用了 DOTS 并行计算的架构开发的。相对 Megacity 来说,这个游戏的场景虽然比较简单,但是里面的动态角色是非常复杂的。

通过上面的两个例子,我们可以得到这样的结论:DOTS 技术非常善于实现同场景中拥有海量实例的行为,并可以极大提升游戏帧率

DOTS 为什么可以做到如此般的效果

Best Hardware Leverage Resources
近些年,CPU 其实已经遇到了一个瓶颈。最近 10 年 CPU 的单核性能没有太多提升,不管是英特尔还是 AMD,其 CPU 性能提升都是通过增加核的数量。现在虽然已经有了 32 核、64 核的 CPU,但是传统的 3D 引擎是很难把这么多核进行利用的。我们常见的引擎可能有主线程、渲染线程,再加一些 worker thread,最多利用十个核之内,无法利用更多的核心数量。DOTS 就能用来解决这个问题,方便开发者实现并行计算的代码。

另外,以往 CPU 都是提供了单指令多数据的向量指令集,但对普通的引擎来讲,非常难把普通的代码进行这种向量化,可能有一部分代码可以向量化,但是向量化的程度并不高,没有把 CPU 里面 SIMD 指令集充分利用起来。DOTS 在这方面也提供了配套的工具。

什么是 DOTS

DOTS - Data Oriented Tech Stack
DOTS 简单的示意图如上所示。最上层蓝色的横条代表我们用 Unity 开发的 3D 程序或游戏。在应用的下层,Unity 提供 ECS(Entity Component System)的框架,基于面向数据(注意这里不是面向对象)的思维设计,方便我们开发面向数据的应用程序。

在 ECS 框架下面,有 Job System,它是方便我们把代码进行 Job 化来进行并行计算的工具。右侧粉红色的方块是 Burst 编译器,这个编译器是帮助我们把开发者写的 C# 代码进行向量化的指令集。

下面依次分析一下这三个部分(ECS, Burst 编译器, Job)。

1. ECS(Entity Component System)

ECS 是 DOTS 的一个基础框架。在分析 ECS 之前,首先分析一下 Data Layout 的重要性。

CPU 里面是有 Cache 的,CPU 代码执行的时候,一般首次去拿数据是要在 CPU 的 Cache 进行访问。

CPU Cache
当代码去访问一个数据的时候,首先会在 Cache 里面寻找有没有这个数据,如果在 Cache 中没有找到,这就叫做 Cache Miss。

Cache Miss
Cache Miss 后,接下来就要到内存里面拷贝一个数据到 CPU 的 Cache 里面,但是这个步骤是非常慢的。当数据从内存拷贝到 CPU 的 Cache 之后,CPU 再从 Cache 里访问这个数据就会非常快。后面如果再去访问这一条 Cache line 中的数据,都会是非常快的。

RAM2Cache
Cache2CPU
开发者经常会遇到一种问题就是,内存是随机分配的,代码去访问内存的时候会随机访问内存里面的地址,导致需要不停从内存拷贝数据到 CPU,造成性能降低。

DOTS 能够解决的是,尽量把数据在内存里面连续存储,把接下来想要处理的一整块数据全部拷贝到 Cache,再去执行这些代码的时候,执行效率就会非常高。

下图是 Unity 传统基于 GameObject 引擎的内存布局。它是一个面向对象编程的设计思想,里面有很多对象,每个对象都有自己不同的数据布局和逻辑代码,都会挂上自己的脚本,处理自己的数据。由于不同的对象是随机分配的,因此内存地址的访问非常慢。

GameObjects
DOTS 使用了一个全新的设计,引入了 ECS(Entity Component System)的三个概念。1. Entity:Entity 本身里面是没有任何数据的,它只是一个用来标记对象的 ID。2. Component:Component 是用来存储数据的容器,每一个颜色的方块都是一个 Component。3. System:System 是用来处理所有数据的逻辑代码。Unity 会把数据以一个对 CPU 非常友好的格式存储。

此外,还有一个概念是 Archetype,如果有很多的 Entity,有些 Entity 可能有 4 种 Component,这些 Entity 就是一种 Archetype。另外一些 Entity 可能有 6 种 Component,就会组成另外一种 Archetype。不同 Archetype 之间可能会共享一些 Component 数据结构,可以去利用这一点来加速计算。

Archetype
我们有了 DOTS 的这种数据格式之后,实际去执行代码的时候会有一个操作叫做 Query,来 Query 需要的数据对象进行处理。

举个例子:可能有 10 种 Archetype,其中可能有 5 种 Archetype 都有 position 这种 Component。当想要处理所有 position 这些数据计算的时候,首先执行 Query,查询所有有 position Component 的这些 Entity,可以把它查询出来,并且连续放在内存里面。

Query
Query 结束之后,下一步就是执行 System 里面的代码,会顺序处理所有的数据。因为这些数据都是连续存储的,会非常快速地拷贝到 CPU 的 Cache 里面,数据计算就会非常迅速。

System - Job
这里有个点需要注意一下。首先,Entity 里面是没有数据的,它和传统的 GameObject 是不一样的。传统 GameObject 每一个对象里面存储了自己的数据,有自己的脚本,去处理自己的业务逻辑,但是 ECS 里面,Entity 是没有数据的,所有的数据放在 Component 里面。System 里面的代码先做 Query,Query 出来需要的数据之后再对它进行处理

Entity Key Remarks

2. Burst 编译器

前文提到,我们老的引擎没有充分利用 SIMD 指令集,Burst 就是用来解决这个问题的。

Burst 简单来说是把 C# 的代码编译成最终的 Native 代码,编译的过程中它会使用专用的指令集进行优化,它底层是基于 LLVM 的一套虚拟机以及它的编译工具链。

Burst 是专门配合 ECS DOTS 技术进行设计的和开发的编译器,它并不是一个通用的编译器,不能用它来编译 Unity ECS 之外的代码,因为为了高性能它是有一些限制的。当然 Burst 也支持 Unity 支持的所有 20 多个平台。

Burst
Burst 可以让我们写的 C# 代码性能非常高,因为做面向数据编程的话,里面的数据大量是向量、矩阵,这种数据是特别适合进行用这种向量指令集进行计算的。其次 Burst 编译器是知道 Unity 内部数据结构的,所以它非常方便做这种数据上的优化。

另外 Unity 还提供了专门的数学库,不管是做向量计算还是矩阵计算,所有数学计算是专门用这种向量指令集进行优化过的,它也会让 Burst 更方便执行。

Burst is Faster
Burst 整个执行过程非常简单,其实就是把 C# 先编译成 .NET 程序集,然后再编译成 LLVM 的中间码,再变成最终的目标平台代码。

Burst Working
Burst 的使用非常简单,只需要在 Job System 代码前面加一个 attribute 表明这段代码是使用 Burst 编译器进行编译的就可以了。

Burst is Easy
当然 Burst 也存在一些限制。首先 Burst 只能编译 Job System 里面的代码,传统的 Monobehaviour 等那些代码是不能编译的。另外在这些代码里面必须使用值类型的数据,里面的数据结构是 Unity 提供的 NativeContainer、NativeArray 等,需要使用我们专用的数据结构,另外不支持一些引用类型的数据。我们把它叫做 high performance 的 C#,相当于是一个删减版。

Burst Constraints
Unity方面做过一些测试,下面是 Burst 优化过的,上面是没有优化过的。单纯就这一个功能是可以让它的帧率有成倍的提升。

Burst Demo

3. Job System

Job System 是帮助我们做并行计算的工具,里面也提供了一些方便写代码的如 Parallel For 等便于开发的语法糖等。

Parallel For
传统的 Unity 开发模式是只使用单线程,即主线程。而 Job System 是 Unity 对多线程(多核使用)能力的封装,且不需要开发者过多的考虑死锁问题。

有关 DOTS1.0

DOTS1.0 是刚刚(2023)发布的版本,Unity 在过去的 5 年中一直在迭代这个技术。之前是面向对象(Gameobjects)的编程,而如今是 ECS,设计思想发生变化,导致 Unity 整个编辑器工具和 DOTS 是不完全兼容的。所以过去 5 年时间,Unity 有很大一部分工作量是面向 DOTS 开发新的编辑器,在 DOTS1.0 里面已经提供了 DOTS 相关的编辑器工具,如下图列表。

DOTS1.0 Editor Tools
虽然 Unity 的整个开发环境都发生变化了,但 Unity 现在提供的方案是让开发者在编辑场景阶段还可以使用传统的 GameObject 的方式进行编辑,编辑完之后有一个转换的过程,可以把它转换成 ECS 格式的数据存储。所以 DOTS 是兼容 Unity 传统的编辑器的。

DOTS Compatibility
在 DOTS1.0 里面,Unity 提供了网络同步的 package,方便开发多人联网游戏,用来做大量玩家的数据同步。使用 DOTS 之后网络游戏玩家数量的规模就可以做得更大。传统 Unity 引擎的一些网络工具可能可以做 16 或 32 人同步的网络游戏,有了 DOTS 之后,Unity 可以做数百人甚至上千人规模的网络同步。

DOTS Netcode
DOTS1.0 里面包含了 DOTS 的物理引擎,Unity 现在全新开发了面向数据的一套物理引擎。另外 Unity 也集成了微软 Havok 的物理引擎,也是以 DOTS 的接口集成到 DOTS 引擎里面的。

Physics Sim
像前文展示的 Megacity 这样的游戏 demo,对整个程序的性能有了多方面的要求。除了渲染之外,未来的元宇宙或游戏可能是上百 G 甚至是上 T 的数据量,不可能全部放在本地硬件上面,需要提供 On-demand Streaming 的能力,动态从云端下载需要的 3D 资源。所以,DOTS 也提供了诸如本地动态加载、云端数据 Streaming,大规模渲染等能力。

Streaming and Rendering

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/394110
推荐阅读
相关标签
  

闽ICP备14008679号