unity DOTS
Data-Oriented Technology Stack (DOTS)中文翻译过来叫做多线程式数据导向型技术堆栈。
DOTS的核心-高性能
- 充分利用多核处理器,多线程处理让游戏的运行速度更快,更高效
DOTS主要由3部分组成
- C#任务系统(Job System),用于高效运行多线程代码。
- 实体组件系统(ECS),用于默认编写高性能代码。
- Burst编译器,用于生成高度优化的本地代码。
Job System
和ECS
是两个不同的概念,两者组合在一起才能发挥最大优势,当然也可以分开使用。
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(…) 给予基础支持
例子
以下分别通过NativeArray
和Array
创建长度为100的数组看看GC的情况。
如下图所示NativeArray
值产生了很小一部分的GC,而数组会产生很多GC!
注意上面在new NativeArray
对象时的Allocator,Allocator.Temp
表示临时对象(分配速度最快),方法体中尽量用它。Allocator.Persistent
则表示持久化对象(分配速度最慢),类的全局对象则使用它。
最后,在Job System
里面传递数据显然要用的就是HPC#的数据结构了。当然其实我们不使用DOTS也是可以单独使用HPC#来优化内存的。
Burst编译器
首先我想先重温下一些概念。
- .NET和C#之间是什么关系?
.NET是Microsoft新一代多语言的开发平台,用于构建和运行应用程序。C#运行在.NET上,如果把C#比作一个舞者,那么.NET就是它的舞台。当然C#不是这个平台上唯一一个舞者!比如还有舞者C++,舞者VB,但是C#是最红的那一个!😏当然.NET除了可以让C#舞动之外,还可以给它提供很多工具表演个情景剧啥的。 - 什么是.NET Framework和.NET Core?
- .NET的实现 如果按操作系统来横向分割的话,可以分为 Windows系统下的 .NET Framework 和 兼容多个操作系统的 .NET Core。
- .NET Framework就是.NET 技术框架组成在Windows系统下的具体的实现,和Windows系统高度耦合。
- 为了能让.NET程序在其它平台上运行,为此开发了在其它平台下的.NET实现——.NET Core。
- 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++。