萤火之森

萤火之森

马上订阅 萤火之森 RSS 更新: http://frankorz.com/atom.xml

Unity DOTS 走马观花

2019年5月8日 05:22

The Big Picture

简单介绍 Data-Oriented Technology Stack (DOTS, 数据导向型技术栈) ,其包含了 C# Job System、the Entity Component System (ECS) 和 Burst。

特点

DOTS 要实现的特点有:

  • **性能的准确性。**我们希望的效果是:如果循环因为某些原因无法向量化,它应该会出现编译器错误,而不是使代码运行速度慢 8 倍,并得到正确结果,完全不报错。
  • 跨平台架构特性。我们编写的输入代码无论是面向 iOS 系统还是 Xbox,都应该是相同的。
  • 我们应该有不错的迭代循环。在修改代码时,可以轻松查看为所有架构生成的机器代码。机器代码“查看器”应该很好地说明或解释所有机器指令的行为。
  • 安全性。大多数游戏开发者不把安全性放在很高的优先级,但我们认为,解决 Unity 出现内存损坏问题是关键特性之一。在运行代码时应该有一个特别模式,如果读取或写入到内存界限外或取消引用 Null 时,它能够提供我们明确的错误信息。

其中向量化指的是 Vectorization。

向量化的相关介绍:

Burst

Unity 构建了名为 Burst 的代码生成器和编译器。

当使用 C# 时,我们对整个流程有完整的控制,包括从源代码编译到机器代码生成,如果有我们不想要的部分,我们会找到并修复它。我们会逐渐把 C++ 语言的性能敏感代码移植为 HPC# (高性能 C#,下文会提到)代码,这样会更容易得到想要的性能,更难出现 Bug,更容易进行处理。

如果 Asset Store 资源插件的开发者在资源中使用 HPC# 代码,资源插件在运行时代码会运行得更快。除此之外,高级用户也会通过使用 HPC# 编写出自定义高性能代码而受益。

ECS Track: Deep Dive into the Burst Compiler - Unite LA

Burst 对于 HPC# 更详细的支持可以在下面找到:

Burst User Guide

深入栈

向量化(Vectorization)无法进行的常见情况是,编译器无法确保二个指针不指向相同的内存,即混淆情况(Alias)。Alias 的问题在 Unity GDC 中也有一个演讲提到过:Unity at GDC - C# to Machine Code

Collections 类就是为了解决这个问题而诞生的,里面包含 NativeList、NativeHashMap<TKey, TValue>、NativeMultiHashMap<TKey, TValue> 和 NativeQueue 四种额外的数据结构。

两个 NativeArray 之间从不会发生混淆这种情况,这也是为什么我们将会经常使用这些数据结构。我们可以在 Burst 中运用这个知识,使它不会由于害怕两个数组指针指向相同内存而放弃优化。

Unity 还编写了 Unity.Mathemetics 数学库,提供了很多像 Shader 代码的数据结构。Burst 也能和这数学库很好的工作,未来 Burst 将能够为 math.sin() 等计算作出牺牲精度的优化。

对于 Burst 而言,math.sin() 不仅是要编译的 C# 方法,Burst 还能理解出 sin() 的三角函数属性,同时知道 x 值较小时会出现 sin(x) 等于 x 的情况,并了解它能替换为泰勒级数展开,以便牺牲特定精度。

跨平台和架构的浮点准确性是 Burst 未来的目标。

传统模式的问题

传统模式指的是什么呢?

  • 跟 MonoBehaviours 打交道
  • 数据和其处理过程耦合在一起
  • 高度依赖引用类型

问题一:数据分布在内存的各个角落

离散的数据导致搜索效率十分低下,还有 Cache Miss 的问题,这个问题可以参考下面的链接:

ECS 的泛泛之谈

问题二:很多不必要的数据也被提供了

例如当我们要调用 Transform 时,可能实际上我们只需要 position 和 rotation 两个属性来移动 gameObject,但是其他不需要的数据也被提供给了 gameObject。

问题三:低效的单线程数据处理

传统模式只使用单线程来按顺序一个一个地处理数据和操作,这样十分低效。

高性能 C#(HPC#)

当我们使用 C# 语言时,仍然无法控制数据在内存中如何进行分布,但这是我们提升性能的关键点。

除此之外,标准库面向的是“堆上的对象”和“具有其它对象指针引用的对象”。

也就是意味着,当处理性能敏感代码时,我们可以放弃使用大部分标准库,例如:Linq、StringFormatter、List、Dictionary。禁止内存分配,即不使用类,只使用结构、映射、垃圾回收器和虚拟调用,并添加可使用的部分新容器,例如:NativeArray 和其他集合类型。

我们可以在越界访问时得到错误和错误信息,以及使用 C++ 代码时的调试器支持和编译速度。我们通常把该子集称为高性能 C# 或 HPC#。

它可以被总结为:

  • 大部分的原始类型(float、int、uint、short、bool…),enums,structs 和其他类型的指针
  • 集合:用 NavtiveArray<T> 代替 T[]
  • 所有的控制流语句(除了 try、finally、foreach、using)
  • throw new XXXException(...) 给予基础支持

Job System

Job System 是针对上述传统模式问题的一种解决方式。例如下图可以把发射子弹看成一个...

剩余内容已隐藏

查看完整文章以阅读更多