unity DOTS

unity DOTS

Data-Oriented Technology Stack (DOTS)中文翻译过来叫做多线程式数据导向型技术堆栈

DOTS的核心-高性能

  • 充分利用多核处理器,多线程处理让游戏的运行速度更快,更高效

DOTS主要由3部分组成

  • C#任务系统(Job System),用于高效运行多线程代码。
  • 实体组件系统(ECS),用于默认编写高性能代码。
  • Burst编译器,用于生成高度优化的本地代码。

Job SystemECS是两个不同的概念,两者组合在一起才能发挥最大优势,当然也可以分开使用。

c#任务系统(Job System)

我们知道unity内部虽然是多线程的(内部c++代码为了性能多线程) ,但是对外的代码是必须跑在主线程上的(unity为了避免死锁,对外提供的c# API都是保证线程安全的)。虽然我们可以使用C#的多线程,但是不能调用unity的API,只能用来处理数据,比如:网络消息,下载等这些和unity无关的的东西。

Job System的诞生让”优雅地”实现多线程变为可能。

Thread和Job System的区别

使用C#的Thread来实现多线程,需要我们自己”加解锁”,但是Job System不需要,系统会自动的帮我们”加解锁”,这样可以大大的避免出错的几率。

但是比较遗憾的是,Job System并非万能,还有很多unity中的API是不能够在其中使用的。比如比较常见的GameObject.Instantiate(),试图在多线程中创建游戏对象是不可以的。


高性能C#

高性能C#(其英文全称为High Performance C# 简称 HPC#)。

C#效率低下原因

C#效率之所以低下的大部分原因都是GC。
因为垃圾回收器在工作的时候,首先需要在内存中找到那些被标记成待删除的数据,才能进行垃圾回收。为了寻找这些数据则需要遍历,这样必然会产生耗时。

除此之外,如何标记待删除的数据?

new一个class的时候除了class本身的资源内存外还需要分配两个额外的内存来对它进行标记,首先需要一个指针,然后还需要一个同步块的索引。这两个额外的内存由CLR进行管理。

可见这样的方式确实会使得我们的效率低下!因此要是能生成不产生GC的数据结构就好了——使用值类型数据会被分配在上是不需要GC的,而是由系统自己处理,所以效率会高很多。

我们知道当我们声明List<int>,int[]这些常见的数组型数据结构时,虽然其中元素是值类型,但是数组本身是一个引用类型,那么它还是会被分配到堆内存上。要想数组也分配在栈上,那么就要使用C#的不安全代码,但是这样很不方便,一些基本集合的操作和维护需要我们自己去实现。
于是unity给我们提供了HPC#方法。(不产生GC,手动释放)


HPC#

对于上面的问题,现在我们可以使用HPC#中的数据结构就可以了。
有这样的一些数据结构(下面只罗列的比较常见的):

  • NativeArray:就是数组,缺点是扩充数组元素需要重新创建NativeArray,并且将原有的数据拷贝进去
  • NativeList:经过包装的数组。
  • NativeQueue:队列。
  • NativeHashMap:哈希表。

注意上面使用的是非托管的数据结构,因此需要我们自己手动来释放内存,请每次使用完毕之后调用Dispose()方法。如果不调用会出现内存泄露。

除了上面提供的这些集合之外,总的来说HPC#的语法可以被概括为:

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

例子

以下分别通过NativeArrayArray创建长度为100的数组看看GC的情况。

如下图所示NativeArray值产生了很小一部分的GC,而数组会产生很多GC!

注意上面在new NativeArray对象时的Allocator,Allocator.Temp表示临时对象(分配速度最快),方法体中尽量用它。Allocator.Persistent则表示持久化对象(分配速度最慢),类的全局对象则使用它。

最后,在Job System里面传递数据显然要用的就是HPC#的数据结构了。当然其实我们不使用DOTS也是可以单独使用HPC#来优化内存的。


Burst编译器

首先我想先重温下一些概念。

  1. .NET和C#之间是什么关系?
    .NET是Microsoft新一代多语言的开发平台,用于构建运行应用程序。C#运行在.NET上,如果把C#比作一个舞者,那么.NET就是它的舞台。当然C#不是这个平台上唯一一个舞者!比如还有舞者C++,舞者VB,但是C#是最红的那一个!😏当然.NET除了可以让C#舞动之外,还可以给它提供很多工具表演个情景剧啥的。
  2. 什么是.NET Framework和.NET Core?
  • .NET的实现 如果按操作系统来横向分割的话,可以分为 Windows系统下的 .NET Framework 和 兼容多个操作系统的 .NET Core。
  • .NET Framework就是.NET 技术框架组成在Windows系统下的具体的实现,和Windows系统高度耦合。
  • 为了能让.NET程序在其它平台上运行,为此开发了在其它平台下的.NET实现——.NET Core。
  1. unity的跨平台技术

    对于unity开发的应用程序,出于对性能的考虑,使用了IL2CPP技术(把IL中间语言转换成CPP文件),在最终打包时把C#代码转换为了C++代码。

    在编辑器模式下,由于不用考虑太多的性能,所以unity还是采用c#的方式运行的,unity编辑器是跨平台的,由于历史原因,目前unity还是运行在Mono(它是非官方社区和组织开发的在其它平台下的.Net实现,而.Net Core是微软官方的亲儿子👶)上的,还没有使用.Net Core的方案。

上面提到的这些技术的性能对比:

  • .Net Core比C++慢2倍
  • Mono比.Net Core慢3倍
  • IL2CPP比Mono快2-3倍
  • 使用Burst编译后可以让C#代码运行效率比C++还快!

注意IL2CPP虽然将IL转换成C++代码,但是实际上它还是模拟了.NET的垃圾回收机制,所以效率并不等同于C++。

更新中。。。

评论