前言
JobSystem并非必须配合ECS使用,他完全可以独立运行在游戏项目中,配合Burst编译能够最大化为我们项目提供多线程支持。使用也较为简单,可以直接视为类似DoTwenn一类的插件放入项目中使用
当前官方还未将JobSystem定位正式版,所以在官方文档上很多地方都较含糊(例如:IJobParallelForBatch、IJobParallelForDefer等),而且还存在一些版本迭代下冗余的方法。
Job概念
Job并非线程,他是运行在线程上的一个个任务(理解为方法更方便,但它是个结构体)。
JobSystem的组成部分有:IJob及其相关变种,JobHandle,NativeArray及其相关变种,Burst特性。
JobHandle
JobHandle即Job在执行过程中返回给我们的操作的状态。最直观来说我们可以通过JobHandle查看Job是否执行完毕。再就是,可以将JobHandle作为其他Job启动的依赖项,这样可以确保后者Job会在JobHandle为执行完毕状态后执行,而非并行执行。
常用技巧
变量
- IsCompleted: 如果该任务当前正在运行,则返回 false。如果该任务已完成,则返回 true。
公共函数
- Complete:在主线程调用该方法后,会等待该Job执行完毕后再执行后续方法。
静态函数:
-
public static JobHandle CombineDependencies (JobHandle job0, JobHandle job1);
public static JobHandle CombineDependencies (NativeArray
jobs); 可以将多个JobHandle合并为一个,然后作为其他Job启动时的JobHandle依赖
-
public static CompleteAll CombineDependencies (ref JobHandle job0, ref JobHandle job1);
public static CompleteAll CombineDependencies (NativeArray
jobs); 在主线程等待所有传入JobHandle为执行完毕状态后,再执行后续方法。
NativeContainer
NativeContainer 是一种托管值类型,为本机内存提供了一个相对安全的 C# 封装器。它包含一个指向非托管分配的指针。
考虑到Job运行在多线程上,会出现数据安全问题——竞争条件(这个不理解的同学需要补一点多线程基础);且Job在运行完毕后会释放自身的数据内存(使用玩即释放),主线程无法访问Job中数据;所以现在需要一种共享内存容器来让主线程与Job共享数据。
NativeContainer 允许在Job中访问与主线程共享的数据,而不是使用常规的引用或内存拷贝。
具体的应用方法见后续IJob介绍部分。
NativeArray
NativeArray<T> array = new NativeArray<T>(int capacity, Allocator allovator);
除构造参数不同,使用上与普通的Array无异。
第一个构造参数count为数组大小,第二个参数allovator为分配内存的存活周期(详情见后文)
需要特别注意:NativeArray在使用完后要调用其
array.Dispose()
方法来释放内存,否则会出现内存泄漏。
Allocator
用于为 NativeArray 指定分配类型。其中Allocator.Temp、Allocator.TempJob、Allocator.Persistent为常用类型
- Allocator.Invalid:无效分配
- Allocator.None:未分配
- Allocator.Temp:临时分配,NativeArray存活周期为1帧。
- Allocator.TempJob:临时Job分配,NativeArray存活周期为4帧,如果第4帧没有调用NativeArray.Dispose,则会报警告
- Allocator.Persistent:永久分配
- Allocator.AudioKernel:与音频相关。
ReadOnly
[ReadOnly]
public NativeArray<int> array;
标记NativeArray在Job中为只读的,默认NativeArray是可读可写的。
NativeArray的其他变种
NativeList
var list = new NativeList<T>(Allocator allocator);
可调整大小的 NativeArray
NativeHashMap
var hashMap = new NativeHashMap<TKey, TValue>(int capacity, Allocator allocator);
键/值对。
NativeMultiHashMap
var multiHashMap = new NativeMultiHashMap<TKey, TValue>(int capacity, Allocator allocator);
每个键有多个值。
NativeQueue
var queue = new NativeQueue<T>(Allocator allocator);
先进先出队列。
IJob
记住所有的IJob都是数据结构。
IJob
IJob特征是将单个Job放在一个线程上去执行,这个线程是随机的(也可能是主线程)。
创建IJob
[BurstCompile]
struct My_IJob : IJob
{
[ReadOnly]
public NativeArray<int> array;
public float deltaTime;
public NativeArray<int> count;
public void Execute()
{
for (int i = 0; i < array.Length; i++)
{
count[0] += array[i];
}
}
}
我们需要声明一个结构体
,并继承IJob
,在结构体内部实现Execute
方法。
Execute
方法内部即我们需要进行计算的代码。
[BurstCompile]
特性有助于我们启动Burst编译时优化该Job。
使用IJob
void Update()
{
int a = 0;
var array = new NativeArray<int>(1000, Allocator.TempJob);
var count = new NativeArray<int>(1, Allocator.TempJob);
for (int i = 0; i < 1000; i++)
{
array[i] = 1;
}
var job = new My_IJob()
{
array = array,
deltaTime = Time.deltaTime,
count = count
};
//1.随机线程执行
JobHandle jobHandle = job.Schedule();
jobHandle.Complete();
//2.直接在当前线程执行Job
//job.Run();
a = job.count[0];
array.Dispose();
count.Dispose();
Debug.Log("job:" + a);
}
job.Schedule();
来执行Job,并返回一个JobHandle。
通过调用jobHandle.Complete();
方法来等待该Job执行完毕。
关于NativeArray,再次声明需要在使用完毕后调用array.Dispose();
释放内存。
完整示范
在计算过程中使用到了math,需要在Unity中导入Mathematics包。
切换OpenJob可以看到Job开启关闭的效果(观察FPS)。
(由于IJob性能上与直接主线程执行没有什么区别,所以看不到太大差距)
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
namespace Demo1
{
class Test_IJob : MonoBehaviour
{
[BurstCompile]
struct My_IJob : IJob
{
[ReadOnly]
public NativeArray<int> array;
public float deltaTime;
//public int count;
//从主线程中访问数据,只能通过Native,所以上面这样直接访问内存是不允许的,只能通过下面这样
public NativeArray<int> count;
public void Execute()
{
for (int i = 0; i < array.Length; i++)
{
count[0] += Compute(i);
}
}
}
public bool OpenJob;
void Update()
{
int a = 0;
//Ijob只是将任务放在一个线程上执行(也有可能是主线程)
//性能上与在主线程执行并无区别
if (OpenJob)
{
var array = new NativeArray<int>(1000, Allocator.TempJob);
var count = new NativeArray<int>(1, Allocator.TempJob);
for (int i = 0; i < 1000; i++)
{
array[i] = 1;
}
var job = new My_IJob()
{
array = array,
deltaTime = Time.deltaTime,
count = count
};
//1.随机线程执行
JobHandle jobHandle = job.Schedule();
jobHandle.Complete();
//2.直接在当前线程执行Job
//job.Run();
a = job.count[0];
array.Dispose();
count.Dispose();
Debug.Log("job:"+a);
}
else
{
int z = 0;
for (int i = 0; i < 1000; i++)
{
z += Compute(i);
}
a = z;
Debug.Log(a);
}
}
static int Compute(int i)
{
// 耗时的计算代码
double value = 0f;
for (int j = 0; j < 1000; j++)
{
value = math.exp10(math.sqrt(value));
}
return i;
}
}
}
IJobFor
IJobFor有三种执行方式,根据执行方式不同,所展现性能也不同。
-
void Run(int arrayLength)
直接在主线程上执行, arrayLength为执行Job
Execute
方法的迭代总次数 -
JobHandle Schedule(int arrayLength, JobHandle dependency)
多个线程顺序执行多个Job,由于是顺序执行,需要等待上个Job执行完成才能执行下个Job,所以性能上与在主线程执行并无区别。 arrayLength为执行Job
Execute
方法的迭代总次数,dependency为依赖的JobHandle -
JobHandle ScheduleParallel(int arrayLength, int innerloopBatchCount, JobHandle dependency)
多个线程同时执行多个Job,是并行运行的,所以性能有所提升。 arrayLength为执行Job
Execute
方法的迭代总次数,innerloopBatchCount为单个Job需要负责的迭代次数,dependency为依赖的JobHandle,简单理接参数:我们原本打算执行100次Job的
Execute
方法,那么arrayLength
填100。我们希望每迭代20次为一个包(这个包就是Job),那么innerloopBatchCount
填入20。这样Job在启动时会自动将100次迭代分成5个20次迭代的Job,然后将这5个Job分发到电脑的线程中,假设我们电脑是四核心,那么就三个线程执行一个Job,还有一个线程执行两个Job,加起来一个五个Job、100次迭代。当然那个线程执行两个Job完全是随机的。
完整示范
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
public class Test_IJobFor : MonoBehaviour
{
[BurstCompile]
struct My_IJobFor : IJobFor
{
[ReadOnly]
public NativeArray<int> array;
public float deltaTime;
public NativeArray<int> count;
public void Execute(int index)
{
count[index] = Compute(index);
}
}
public bool OpenJob;
private void Update()
{
int a = 0;
if (OpenJob)
{
var array = new NativeArray<int>(1000, Allocator.TempJob);
var count = new NativeArray<int>(1000, Allocator.TempJob);
for (int i = 0; i < 1000; i++)
{
array[i] = 1;
}
var job = new My_IJobFor()
{
array = array,
deltaTime = Time.deltaTime,
count = count
};
JobHandle sheduleJobDependency = new JobHandle();
///三种执行方式:
//1.多个线程顺序执行多个Job,由于是顺序执行,需要等待上个Job执行完成才能执行下个Job,所以性能上与在主线程执行并无区别
//JobHandle sheduleJobHandle = job.Schedule(array.Length, sheduleJobDependency);
//2.并行 (迭代总次数,每个Job执行负责迭代次数,依赖的Job句柄)
//将遍历总次数按批次平分成包,将不同的包放入不同线程中执行(有可能是主线程)。
JobHandle sheduleJobHandle = job.ScheduleParallel(array.Length, 10, sheduleJobDependency);
sheduleJobHandle.Complete();
//2.直接在当前线程执行Job
//job.Run(array.Length);
for (int i = 0; i < count.Length; i++)
{
a += count[i];
}
array.Dispose();
count.Dispose();
Debug.Log("job:" + a);
}
else
{
int z = 0;
for (int i = 0; i < 1000; i++)
{
z += Compute(i);
}
a = z;
Debug.Log(a);
}
}
static int Compute(int i)
{
// 耗时的计算代码
double value = 0f;
for (int j = 0; j < 1000; j++)
{
value = math.exp10(math.sqrt(value));
}
return i;
}
}
IJobParallelFor
IJobParallelFor执行效率同IJobFor.ScheduleParallel(),都是并行执行,但是精简了JobHandle依赖参数。可以说IJobFor.ScheduleParallel()是版本迭代下来冗余的产物,完全可以被IJobParallelFor取代。
完整示范
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
public class Test_IJobParallelFor : MonoBehaviour
{
[BurstCompile]
struct My_IJobParallelFor : IJobParallelFor
{
[ReadOnly]
public NativeArray<int> array;
public float deltaTime;
public NativeArray<int> count;
public void Execute(int index)
{
count[index] = Compute(index);
}
}
public bool OpenJob;
private void Update()
{
int a = 0;
if (OpenJob)
{
var array = new NativeArray<int>(1000, Allocator.TempJob);
var count = new NativeArray<int>(1000, Allocator.TempJob);
for (int i = 0; i < 1000; i++)
{
array[i] = 1;
}
var job = new My_IJobParallelFor()
{
array = array,
deltaTime = Time.deltaTime,
count = count
};
//1.在多线程中执行job (迭代总次数,每个Job执行负责迭代次数)
JobHandle jobHandle = job.Schedule(array.Length, 10);
jobHandle.Complete();
//2.直接在当前线程执行Job
//job.Run(array.Length);
for (int i = 0; i < count.Length; i++)
{
a += count[i];
}
array.Dispose();
count.Dispose();
Debug.Log("job:" + a);
}
else
{
int z = 0;
for (int i = 0; i < 1000; i++)
{
z += Compute(i);
}
a = z;
Debug.Log(a);
}
}
static int Compute(int i)
{
// 耗时的计算代码
double value = 0f;
for (int j = 0; j < 1000; j++)
{
value = math.exp10(math.sqrt(value));
}
return i;
}
}
IJobParallelForTransform
IJobParallelForTransform可以在Job运行时提供transform读写支持。
TransformAccessArray
TransformAccessArray是一个可以在Job和主线程中共享Transform的List容器,可以理接为List<Transform>
。
var accessArray = new TransformAccessArray(Transform[] transforms, int desiredJobCount = -1);
var accessArray = new TransformAccessArray(int capacity, int desiredJobCount = -1);
构造参数:capacity为初始话容量,后续容量不够会自动扩充,这点与List类似。desiredJobCount为单个Job负责迭代次数。
作为多线程共享容器,与NativeArray
相同,需要在使用完毕后调用accessArray.Dispose();
方法释放内存。
在调用IJobParallelForTransform.Schedule()
方法执行Job时TransformAccessArray
作为参数提供支持
JobHandle jobHandle = job.Schedule(accessArray);
完整案例
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Jobs;
public class Test_IJobParallelForTransform : MonoBehaviour
{
[BurstCompile]
struct My_IJobParallelForTransform : IJobParallelForTransform
{
[ReadOnly]
public NativeList<Vector3> list;
public float deltaTime;
public void Execute(int index, TransformAccess transform)
{
transform.position += (list[index]*deltaTime);
Compute(index);
}
}
public bool OpenJob;
public GameObject prefab;
public int prefabCount=1000;
private TransformAccessArray accessArray;
private NativeList<Vector3> list;
private void Start()
{
//参数:初始容量(不够了会自动扩充),迭代批次
accessArray = new TransformAccessArray(prefabCount,20);
list = new NativeList<Vector3>(Allocator.Persistent);
for (int i = 0; i < prefabCount; i++)
{
var transform = GameObject.Instantiate(this.prefab, UnityEngine.Random.insideUnitSphere * 100, Quaternion.identity).transform;
accessArray.Add(transform);
list.Add(UnityEngine.Random.insideUnitSphere);
}
}
private void Update()
{
if (OpenJob)
{
var job = new My_IJobParallelForTransform()
{
list = list,
deltaTime = Time.deltaTime
};
JobHandle jobHandle = job.Schedule(accessArray);
jobHandle.Complete();
Debug.Log("job");
}
else
{
for (int i = 0; i < accessArray.length; i++)
{
accessArray[i].position += (list[i] * Time.deltaTime);
Compute(i);
}
Debug.Log("");
}
}
static int Compute(int i)
{
// 耗时的计算代码
double value = 0f;
for (int j = 0; j < 1000; j++)
{
value = math.exp10(math.sqrt(value));
}
return i;
}
private void OnDestroy()
{
//记得释放内存
accessArray.Dispose();
list.Dispose();
}
}
IJobParallelForBatch
IJobParallelForBatch与IJobParallelFor最大区别就是可以跨批次迭代。
通俗点理接见下面代码:
void IJobParallelFor.Schedule(int arrayLength, int innerloopBatchCount)
{
for (int i = 0; i < arrayLength; i++)
{
Job.Execute(i);
}
}
void IJobParallelForBatch.Schedule(int arrayLength, int innerloopBatchCount)
{
for (int i = 0; i < (arrayLength/innerloopBatchCount); i++)
{
Job.Execute(i, innerloopBatchCount);
}
}
完整案例
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
public class Test_IJobParallelForBatch : MonoBehaviour
{
[BurstCompile]
struct My_IJobParallelForBatch : IJobParallelForBatch
{
[ReadOnly]
public NativeArray<int> array;
public float deltaTime;
public NativeArray<int> sum;
public void Execute(int startIndex, int count)
{
Debug.Log($"startIndex:{startIndex},count:{count}");
for (int i = startIndex; i < startIndex + count; i++)
{
sum[i] = Compute(i);
}
}
}
[BurstCompile]
struct My_IJobParallelFor : IJobParallelFor
{
[ReadOnly]
public NativeArray<int> array;
public float deltaTime;
public NativeArray<int> count;
public void Execute(int index)
{
Debug.Log($"index{index}");
count[index] = Compute(index);
}
}
public bool OpenForBatch;
private void Update()
{
int a = 0;
if (OpenForBatch)
{
var array = new NativeArray<int>(23, Allocator.TempJob);
var count = new NativeArray<int>(23, Allocator.TempJob);
for (int i = 0; i < 23; i++)
{
array[i] = 1;
}
var job = new My_IJobParallelForBatch()
{
array = array,
deltaTime = Time.deltaTime,
sum = count
};
//在多线程中执行job (迭代总次数,每个Job执行负责迭代次数)
JobHandle jobHandle = job.ScheduleBatch(array.Length, 5);
jobHandle.Complete();
for (int i = 0; i < count.Length; i++)
{
a += count[i];
}
array.Dispose();
count.Dispose();
Debug.Log("ForBatch:::::::" + a);
}
else
{
var array = new NativeArray<int>(23, Allocator.TempJob);
var count = new NativeArray<int>(23, Allocator.TempJob);
for (int i = 0; i < 23; i++)
{
array[i] = 1;
}
var job = new My_IJobParallelFor()
{
array = array,
deltaTime = Time.deltaTime,
count = count
};
//在多线程中执行job (迭代总次数,每个Job执行负责迭代次数)
JobHandle jobHandle = job.Schedule(array.Length, 5);
jobHandle.Complete();
for (int i = 0; i < count.Length; i++)
{
a += count[i];
}
array.Dispose();
count.Dispose();
Debug.Log("JobFor:::::::" + a);
}
}
static int Compute(int i)
{
// 耗时的计算代码
double value = 0f;
for (int j = 0; j < 1000; j++)
{
value = math.exp10(math.sqrt(value));
}
return i;
}
}
IJobParallelForDefer
IJobParallelForDefer与IJobParallelFor使用方法基本相同,唯一不同的是传入的迭代总次数为指针,并且可以在调用job.Schedule();
方法后修改指针,以此来达到Job运行时动态修改迭代次数的目的。
需要注意的是,在C#中使用指针需要在Unity中开启不安全代码许可,并在方法前加上unsafe
关键字。
完整案例
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
public class Test_IJobParallelForDefer : MonoBehaviour
{
[BurstCompile]
struct My_IJobParallelForDefer : IJobParallelForDefer
{
[ReadOnly]
public NativeArray<int> array;
public float deltaTime;
public NativeArray<int> count;
public void Execute(int index)
{
Debug.Log($"index{index}");
count[index] = Compute(index);
}
}
[BurstCompile]
struct My_IJobParallelFor : IJobParallelFor
{
[ReadOnly]
public NativeArray<int> array;
public float deltaTime;
public NativeArray<int> count;
public void Execute(int index)
{
Debug.Log($"index{index}");
count[index] = Compute(index);
}
}
public bool OpenForDefer;
unsafe private void Update()
{
int a = 0;
if (OpenForDefer)
{
int length = 25;
int* _Length = &length;
var array = new NativeArray<int>(length, Allocator.TempJob);
var count = new NativeArray<int>(length, Allocator.TempJob);
for (int i = 0; i < length; i++)
{
array[i] = 1;
}
var job = new My_IJobParallelForDefer()
{
array = array,
deltaTime = Time.deltaTime,
count = count
};
//在多线程中执行job (迭代总次数,每个Job执行负责迭代次数)
//可以传入指针,允许在调用Schedule之后修改指针引用来改变遍历次数
JobHandle jobHandle = job.Schedule(_Length, 5);
length = 20;
jobHandle.Complete();
for (int i = 0; i < count.Length; i++)
{
a += count[i];
}
array.Dispose();
count.Dispose();
Debug.Log("ForDefer:::::::" + a);
}
else
{
int length = 25;
var array = new NativeArray<int>(length, Allocator.TempJob);
var count = new NativeArray<int>(length, Allocator.TempJob);
for (int i = 0; i < length; i++)
{
array[i] = 1;
}
var job = new My_IJobParallelFor()
{
array = array,
deltaTime = Time.deltaTime,
count = count
};
//在多线程中执行job (迭代总次数,每个Job执行负责迭代次数)
JobHandle jobHandle = job.Schedule(length, 5);
length = 20;
jobHandle.Complete();
for (int i = 0; i < count.Length; i++)
{
a += count[i];
}
array.Dispose();
count.Dispose();
Debug.Log("JobFor:::::::" + a);
}
}
static int Compute(int i)
{
// 耗时的计算代码
double value = 0f;
for (int j = 0; j < 1000; j++)
{
value = math.exp10(math.sqrt(value));
}
return i;
}
}
IJobParallelForFilter
IJobParallelForFilter用于对NativeList<int>
的元素进行筛选操作。
JobParallelForFilter.Execute()
方法会返回一个bool参数,job会根据bool的真假操作List中的该元素。
bool IJobParallelForFilter.Execute(int index){
}
IJobParallelForFilter有两种执行方式:
-
IJobParallelForFilter(NativeList
indices, int innerloopBatchCount) 该方法会在job的
Execute(int index)
返回为false
时,调用参数indices.Remove(index)
的方法,删除List中该元素。需要注意的是,这时候
IJobParallelForFilter.Execute(int index)
中的参数index
并非List的指数,而是List的元素。 -
ScheduleAppend(NativeList
indices, int arrayLength, int innerloopBatchCount) 该方法会在job的
Execute(int index)
返回为true
时,调用参数indices.Add(index)
的方法,将指数作为元素添加到List中。将当前遍历的指数(即
IJobParallelForFilter.Execute(int index)
的index
)为从 0 到参数arrayLength。
完整案例
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
public class Test_IJobParallelForFilter : MonoBehaviour
{
[BurstCompile]
struct My_IJobParallelForFilter : IJobParallelForFilter
{
[ReadOnly]
public NativeArray<int> array;
public float deltaTime;
//需要注意的是,这里的index并非传入List<int>的指针,而是List<int>的元素
bool IJobParallelForFilter.Execute(int index)
{
Compute(index);
//如果Index为偶数返回真
if (array[index] % 2 != 0)
return false;
return true;
}
}
public bool FilterOrAppend;
unsafe private void Update()
{
int length = 25;
if (FilterOrAppend)
{
var list = new NativeList<int>(length,Allocator.TempJob);
var array = new NativeArray<int>(length, Allocator.TempJob);
for (int i = 0; i < length; i++)
{
list.Add(i);
array[i] = i+100;
}
var job = new My_IJobParallelForFilter()
{
array = array,
deltaTime = Time.deltaTime,
};
Debug.Log("执行前List容量:::::::" + list.Length);
//在多线程中执行job (操作的List<int>,每个Job执行负责迭代次数)
//该方法会根据Job的Execute()方法中返回的Bool,对List<int>的元素进行剔除。
JobHandle jobHandle = job.ScheduleFilter(list, 5);
jobHandle.Complete();
for (int i = 0; i < list.Length; i++)
{
Debug.Log("ScheduleFilter:"+list[i]);
}
Debug.Log("执行后List容量:::::::" + list.Length);
array.Dispose();
list.Dispose();
}
else
{
var list = new NativeList<int>(length, Allocator.TempJob);
var array = new NativeArray<int>(length, Allocator.TempJob);
for (int i = 0; i < length; i++)
{
array[i] = i+100;
}
var job = new My_IJobParallelForFilter()
{
array = array,
deltaTime = Time.deltaTime,
};
Debug.Log("执行前List容量:::::::" + list.Length);
//在多线程中执行job (操作的List<int>,迭代总次数,每个Job执行负责迭代次数)
//该方法会根据Job的Execute()方法中返回的Bool,增加将迭代指数(即Index)Add到List<int>中。
JobHandle jobHandle = job.ScheduleAppend(list,length, 5);
jobHandle.Complete();
for (int i = 0; i < list.Length; i++)
{
Debug.Log("ScheduleAppend:" + list[i]);
}
Debug.Log("执行后List容量:::::::" + list.Length);
array.Dispose();
list.Dispose();
}
}
static int Compute(int i)
{
// 耗时的计算代码
double value = 0f;
for (int j = 0; j < 1000; j++)
{
value = math.exp10(math.sqrt(value));
}
return i;
}
}
Burst
Burst编译系统,开启后能够进一步优化C#的语法,提升Job的运行性能。
前面声明Job结构体时我们已经看到了,在结构体上方有[BurstCompile]
特性
[BurstCompile]
struct My_IJobFor : IJobParallelFor
{
......
现在我们在Unity中开启它,在菜单栏-》Jobs-》Burst 下勾选Enable Commpilation