查看原文
其他

面向数据技术栈DOTS的C++和C#

Unity Unity官方平台 2022-05-07

在Unity在GDC 2019活动日程中,有一项重要的技术演讲,即分享:连接DOTS:Unity面向数据技术栈,帮助你了解DOTS技术如何应用于Megacity示例项目。


你也许会疑问:什么是DOTS?本文将为你介绍Unity全新的面向数据技术栈DOTS,并分享当前技术的进展以及未来的发展方向。


C++和C#

首先让我们谈谈C++,它是编写Unity的语言。

 

许多高级游戏程序员遇见的一个常见问题是:他们需要提供带有目标处理器,可理解指令的可执行程序,执行时会运行游戏。

 

对于代码中性能影响较大的部分,我们明白最终的指令应该是什么。我们希望以一种简单合理方式来描述逻辑,然后确认并验证生成我们希望的指令。

 

在我们看来,C++在这项任务上并不出色。假设我们希望让循环进行向量化,但可能会出现超级多的情况使编译器无法向量化循环。甚至之前可以成功向量化,但由于一个看似无关紧要的改动却无法再次向量化。让所有C/C++编译器都成功向量化代码是一件困难的事情。

 

我们决定自己实现“生成机器代码的合理方法”,该方法会具有我们需要的所有特点。我们也可以耗费大量精力来调整C++代码的设计,稍微改进其工作效果,但我们更想把精力用在开发可实现所有设计的工具链,针对游戏开发者遇到的问题来进行设计。


实现特点

我们要实现的特点有:


  • 性能的准确性。我们希望的效果是:如果循环因为某些原因无法向量化,它应该会出现编译器错误,而不是使代码运行速度慢8倍,并得到正确结果,完全不报错。

     

  • 跨平台架构特性。我们编写的输入代码无论是面向iOS系统还是Xbox,都应该是相同的。

     

  • 我们应该有不错的迭代循环。在修改代码时,可以轻松查看为所有架构生成的机器代码。机器代码“查看器”应该很好地说明或解释所有机器指令的行为。

     

  • 安全性。大多数游戏开发者不把安全性放在很高的优先级,但我们认为,解决Unity出现内存损坏问题是关键特性之一。在运行代码时应该有一个特别模式,如果读取或写入到内存界限外或取消引用Null时,它能够提供我们明确的错误信息。


语言选择

明确需要的特点后,下一步是决定机器代码生成器所使用的输入语言。


我们有三个选择:

  • 自定义语言

  • C或C++的改编或子集

  • C#的子集

 

你或许会想:难道要为性能最敏感的内部循环使用C#语言?没错。


C#是非常自然的选择,它为Unity提供了很多好处: 

  • C#是Unity用户的常用语言。

  • C#有很好的IDE工具,包括:编辑,重构和调试等功能。

  • 我们已经有了C#转换为中间语言的编译器,即微软的Roslyn C#编译器,我们可以使用该编译器,而不必重新编写。

  • 我们有大量修改中间语言的经验,因此对实际程序进行代码生成和后期处理会简单许多。

  • 使用C#能避免C++的问题,例如:头文件包含问题、PIMPL模式、漫长的编译时间等。

 

我们很喜欢使用C#语言进行编码,但是从性能角度看,传统的C#不是很好的语言。


C#语言团队,标准库团队和运行时团队在过去二年取得了很大进展。但是,当我们使用C#语言时,仍然无法控制数据在内存中如何进行分布,但这是我们提升性能的关键点。

 

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

 

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


这样剩余C#语言会有很好的效果,该子集让我们在常用循环中实现所有需要的功能。因为它是C#的有效子集,所以我们也可以把它作为常规C#代码来运行。


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


Burst编译器开发进展

我们构建了名为Burst的代码生成器和编译器,它自从Unity 2018.1开始作为预览资源包推出。

 

Burst有时运行速度比C++快,有时也会比C++慢。后面的情况源于性能问题,我们将在之后解决它。

 

仅对比性能是不够的,同样重要的还有为了取得性能所要做的事情。例如:我们曾把C++渲染器的剔除代码移植到Burst,我们得到了相同的性能,但是C++版要进行大量处理才能使C++编译器进行向量化,而Burst版的代码约不到C++版的1/4。

 

并非所有Unity内部成员都支持“应该把性能最敏感的代码移植到C#”的说法。对于我们多数人来说,这种说法听起来就像说,使用C++时“代码和Metal接口更近”,但这种情况不会持续太久。


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

 

下图是Burst检视窗口的截图,你可以轻松查看它为不同Burst常用循环生成的程序集指令。

 


Unity有大量不同水平的用户,有些用户可以通过记忆列举出整个Arm64指令集,有些用户喜欢开发创造本身,但所有用户都会受益于运行引擎代码的帧时间变快的效果。


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


优化粒度

在C++中,要求编译器对项目的不同部分进行不同的优化取舍是很困难的,最好的情况是根据指定优化等级的文件粒度来处理。

 

Burst被设计为把该程序的单个方法作为输入,即常用循环的切入点。Burst将会编译函数及其调用的所有内容。

  

因为Burst仅处理程序的较小部分,我们把优化等级设为11。Burst会内联每个调用位置,我们要移除IF检查,否则它不会被自动移除,因为在内联形式中,我们有更多函数参数的信息。


对解决常见多线程问题的帮助

C++和C#都无法为开发者编写线程安全代码提供太多帮助。即使在今天,拥有多个核心游戏消费级硬件发展至今已经过去了十年,但依旧很难有效处理使用多个核心的程序。

 

数据冲突,不确定性和死锁是使多线程代码难以编写的挑战。我们想要的特性是“确保代码调用的函数和所有内容不会在全局状态下读取或写入”。我们希望违反规则的是编译器错误,而不是“程序员应遵守的准则”,Burst则会提供编译器错误。

 

我们鼓励Unity用户编写“作业化”代码:将所有需要发生的数据转换划分为作业


每个作业都具有“功能性”,因为作业没有副作用。作业会明确指定使用的只读缓冲区和读写缓冲区,尝试访问其它数据会出现编译器错误。

 

作业调度程序会确保在作业运行时,任何程序都不会写入只读缓冲区。我们会确保在作业运行时,任何程序都不会读取读写缓冲区。

 

如果调度的作业违反了这些规则,我们会得到运行时错误。这种错误不仅在竞态条件下得到,错误信息会说明,你正在尝试调度的作业想要读取缓冲区A,但你之前已经调度了会写入缓冲区A的作业,所以如果想要执行该操作,需要把之前的作业指定为依赖。

 

我们发现这种安全机制可以在提交代码前检查到大量Bug,并高效使用所有内核。这样我们就无法编写出死锁或竞态条件。这样也确保运行结果具有确定性,无论正在运行多少线程,或线程被其它过程中断多少次。


处理栈

由于能够处理所有组件,我们可以使这些组件了解各自的存在。例如:向量化无法进行的常见情况是,编译器无法确保二个指针不指向相同的内存,即混淆情况。

 

我们知道,二个NativeArray从不会混淆,因为已经编写了集合库,我们可以在Burst中运用这个知识,使它不会由于害怕二个数组指针指向相同内存而放弃优化。

 

类似的,我们编写了Unity.Mathemetics数学库。Burst拥有数学库的丰富知识,未来Burst将能够为math.sin()等计算作出牺牲精度的优化。


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

 

跨平台和架构的浮点准确性也是Burst未来的目标,我们相信这是可以实现的目标。


消除引擎代码和游戏代码的区别

通过使用HPC#编写Unity的运行时代码,引擎和游戏会使用相同语言来编写。

 

我们将会发布源代码转换为HPC#的运行时系统,这样每个人都可以从中学习、改进和调整,用户可以编写更好的粒子系统,物理系统和渲染器。

 

通过让我们的内部开发过程更像用户的开发过程,我们会更直接地感受用户的痛点,我们可以集中精力改进单个工作流程,而不是二个不同的工作流程。


小结

关于Unity全新的面向数据技术栈DOT的进展以及未来的发展方向为大家介绍到这里,后续的系列文章中,我们会介绍DOTS的不同部分-实体组件系统,尽请期待。

 

更多Unity技术文章分享,尽在Unity Connect平台(Connect.unity.com)。


推荐阅读

官方活动

Unity GDC 2019 活动日程

3月18-22日GDC 2019将在旧金山举行,了解Unity活动日程信息,请点击此处

Unity GDC 2019官网:

https://unity.com/gdc-2019


Unity客户关怀专享会报名开启

3月15日,Unity将在上海举办Unity客户关怀专享会,此次活动将会为开发者和Unity官方搭建顺畅沟通的桥梁。[了解详情...

报名截至时间:3月11日 8:00

报名链接:https://connect.unity.com/events/2019ShanghaiRoadShow


Unite Shanghai 2019

5月10日-12日上海,Unite大会强势回归。技术门票正在热销中,购票即获指定Asset Store资源商店精品21款资源的5折优惠券。

购票请访问:Unite2019.csdn.net



点击“阅读原文”访问Unity Connect

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存