Created
July 10, 2017 20:20
-
-
Save valyard/e96060ee8e5a9d52d43901c7c6ad4288 to your computer and use it in GitHub Desktop.
"Slides" about C# Job System and ESC in Unity.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ###################################################################### | |
// | |
// We want you to write more efficient code | |
// | |
// ###################################################################### | |
But, we teach the opposite... | |
You know, GameObjects and Components. | |
... | |
Otherwise, it feels not Unity way. | |
// ###################################################################### | |
// | |
// To fully utilize CPU and all its cores you need: | |
// | |
// ###################################################################### | |
1. Control over data and data layout. | |
2. Multi-threaded code which can execute in parallel. | |
3. Generate more efficient code. | |
// Answers: | |
1. Data-oriented design - we want to teach you how to structure your code properly. | |
2. C# Job System — safe and efficient way to write multi-threaded code. | |
3. Jobs compiler technology. | |
But... You will need to change the way you think about code. | |
// ###################################################################### | |
// | |
// [DEMO] | |
// | |
// ###################################################################### | |
// ###################################################################### | |
// | |
// Data-oriented design | |
// | |
// ###################################################################### | |
CPUs like their data sequential: | |
/[data] [data] [data] [data] [data] [data] [data] [data] | |
This layout will result in many cache misses: | |
/[data] ...... ...... ...... ...... [data] ...... ...... | |
/...... ...... [data] ...... [data] ...... ...... ...... | |
...... [data] ...... ...... [data] [data] ...... [data] | |
// ###################################################################### | |
// | |
// Old school object-oriented code | |
// A MonoBehaviour on every GameObject. | |
// Update() is called on every one of them. | |
// | |
// ###################################################################### | |
public class Rotator : MonoBehaviour | |
{ | |
public float speed; | |
private void Update() | |
{ | |
transform.rotation = transform.rotation * | |
Quaternion.AngleAxis(speed * Time.deltaTime, Vector3.up); | |
} | |
} | |
// ###################################################################### | |
// | |
// Will have huge overhead when the number of GameObjects grows. | |
// http://blogs.unity3d.com/2015/12/23/1k-update-calls/ | |
// | |
// ###################################################################### | |
// ###################################################################### | |
// | |
// Manager for all Rotators. | |
// | |
// ###################################################################### | |
public class Rotator : MonoBehaviour | |
{ | |
private RotatorManager m_Manager; | |
[SerializeField] | |
private float m_Speed; | |
private int m_Index = -1; | |
public float speed | |
{ | |
get { return m_Speed; } | |
set | |
{ | |
m_Speed = value; | |
if (m_Index != -1) | |
m_Manager.SetSpeed(m_Index, value); | |
} | |
} | |
private void OnEnable() | |
{ | |
m_Manager = RotatorManager.Instance; | |
m_Index = m_Manager.Add(transform, m_Speed); | |
} | |
private void OnDisable() | |
{ | |
m_Manager.Remove(this); | |
} | |
} | |
class RotatorManager : MonoBehaviour | |
{ | |
List<Transform> m_Transforms; | |
List<float> m_Speeds; | |
public int Add(Transform transform, float speed) | |
{ | |
m_Speeds.Add(speed); | |
m_Transforms.Add(transform); | |
return m_Transforms.Count - 1; | |
} | |
public void SetSpeed(int index, float speed) | |
{ | |
m_Speeds[index] = speed; | |
} | |
... | |
private void Update() | |
{ | |
float deltaTime = Time.deltaTime; | |
for (int i = 0; i != m_Transforms.Count; i++) | |
{ | |
var transform = m_Transforms[i]; | |
transform.rotation = transform.rotation * | |
Quaternion.AngleAxis (m_Speeds[i] * deltaTime, Vector3.up); | |
} | |
} | |
} | |
// ###################################################################### | |
// | |
// Entity-Component-System code | |
// | |
// ###################################################################### | |
// Data is in Components | |
// Logic is in Systems | |
public class RotationSpeedComponent : MonoBehaviour | |
{ | |
public float speed; | |
} | |
public class RotatingSystem : ComponentSystem | |
{ | |
// NOTE: InjectTuples scans all [InjectTuples] in the class | |
// and returns the union of objects that have both Transform and LightRotator | |
[InjectTuples] | |
public ComponentArray<Transform> m_Transforms; | |
[InjectTuples] | |
public ComponentArray<RotationSpeedComponent> m_Rotators; | |
override protected void OnUpdate() | |
{ | |
base.OnUpdate (); | |
float dt = Time.deltaTime; | |
for (int i = 0; i != m_Transforms.Length;i++) | |
{ | |
m_Transforms[i].rotation = m_Transforms[i].rotation * | |
Quaternion.AngleAxis(dt * m_Rotators[i].speed, Vector3.up); | |
} | |
} | |
} | |
// ###################################################################### | |
// | |
// Lightweight components | |
// | |
// ###################################################################### | |
[Serializable] | |
public struct RotationSpeed : IComponentData | |
{ | |
public float speed; | |
public RotationSpeed (float speed) { this.speed = speed; } | |
} | |
[UnityEngine.ExecuteInEditMode] | |
public class RotationSpeedDataComponent : ComponentDataWrapper<RotationSpeed> { } | |
public class RotatingSystem : ComponentSystem | |
{ | |
// NOTE: InjectTuples scans all [InjectTuples] in the class | |
// and returns the union of objects that have both Transform and LightRotator | |
[InjectTuples] | |
public ComponentArray<Transform> m_Transforms; | |
[InjectTuples] | |
public ComponentDataArray<RotationSpeed> m_Rotators; | |
override protected void OnUpdate() | |
{ | |
base.OnUpdate (); | |
float dt = Time.deltaTime; | |
for (int i = 0; i != m_Transforms.Length;i++) | |
{ | |
m_Transforms[i].rotation = m_Transforms[i].rotation * | |
Quaternion.AngleAxis(dt * m_Rotators[i].speed, Vector3.up); | |
} | |
} | |
} | |
// ###################################################################### | |
// | |
// Lightweight jobified components | |
// | |
// ###################################################################### | |
public class SystemRotator : ComponentSystem | |
{ | |
[InjectTuples] | |
public TransformAccessArray m_Transforms; | |
[InjectTuples] | |
public ComponentDataArray<RotationSpeed> m_Rotators; | |
protected override void OnUpdate() | |
{ | |
base.OnUpdate (); | |
var job = new Job(); | |
job.dt = Time.deltaTime; | |
job.rotators = m_Rotators; | |
AddDependency(job.Schedule(m_Transforms, GetDependency())); | |
} | |
struct Job : IJobParallelForTransform | |
{ | |
public float dt; | |
[ReadOnly] | |
public ComponentDataArray<RotationSpeed> rotators; | |
public void Execute(int i, TransformAccess transform) | |
{ | |
transform.rotation = transform.rotation * | |
Quaternion.AngleAxis(dt * rotators[i].speed, Vector3.up); | |
} | |
} | |
} | |
// ###################################################################### | |
// | |
// What is a Job? | |
// | |
// ###################################################################### | |
// Always a struct | |
struct CopyFloatsJob : IJob | |
{ | |
// All the data must be declared here | |
// All data must be blittable | |
[ReadOnly] // Declare how we are going to use this data | |
public NativeArray<float> src; | |
public NativeArray<float> dst; | |
public void Execute() | |
{ | |
for (int i = 0; i < src.Length; i++) | |
dst[i] = src[i]; | |
} | |
} | |
// Fill job data | |
var job = new CopyFloatsJob() | |
{ | |
src = new NativeArray<float>(500, Allocator.Temp), | |
dst = new NativeArray<float>(500, Allocator.Temp) | |
} | |
// Run the job | |
var jobHandle = job.Schedule(); | |
// Need results ready immediately! | |
jobHandle.Complete(); | |
// ###################################################################### | |
// | |
// Native containers | |
// | |
// ###################################################################### | |
// It's all about taking care of your memory. | |
NativeArray<T> | |
NativeList<T> | |
NativeSlice<T> | |
NativeHashMap<TKey, TValue> | |
NativeMultiHashMap<TKey, TValue> | |
var array = new NativeArray<int>(100, Allocator.Temp); | |
array[0] = 42; | |
array.Dispose(); | |
// ###################################################################### | |
// | |
// For loop on different threads | |
// | |
// ###################################################################### | |
struct CopyFloatsJob : IJobParallelFor | |
{ | |
[ReadOnly] | |
public NativeArray<float> src; | |
public NativeArray<float> dst; | |
public void Execute(int index) | |
{ | |
dst[index] = src[index]; | |
} | |
} | |
var job = new CopyFloatsJob() | |
{ | |
src = new NativeArray<float>(500, Allocator.Temp), | |
dst = new NativeArray<float>(500, Allocator.Temp) | |
} | |
// (number of items to iterate, batch size) | |
var jobHandle = job.Schedule(500, 100); | |
jobHandle.Complete(); | |
// IJobParallelFor for Transforms | |
struct Job : IJobParallelForTransform | |
{ | |
public float dt; | |
[ReadOnly] | |
public ComponentDataArray<RotationSpeed> rotators; | |
public void Execute(int i, TransformAccess transform) | |
{ | |
transform.rotation = transform.rotation * | |
Quaternion.AngleAxis(dt * rotators[i].speed, Vector3.up); | |
} | |
} | |
// ###################################################################### | |
// | |
// Dependencies | |
// | |
// ###################################################################### | |
var source = new NativeArray<float>(500, Allocator.Temp); | |
var tmp = new NativeArray<float>(500, Allocator.Temp); | |
// The first job | |
var job1 = new CopyFloatsJob() { src = source, dst = tmp }; | |
var job1Handle = job1.Schedule(src.Length, 100); | |
var destination = new NativeArray<float>(500, Allocator.Temp); | |
// The second job | |
var job2 = new CopyFloatsJob() { src = tmp, dst = destination }; | |
var job2Handle = job2.Schedule(src.Length, 100, job1Handle); | |
job2Handle.Complete(); | |
// ###################################################################### | |
// | |
// Safety | |
// | |
// ###################################################################### | |
We want Unity to be a Sandbox. | |
If you make a mistake we want to tell you what you did wrong. | |
C# job system will give you an time error if you: | |
* Incorrectly set up dependencies, | |
* Access the same collection in parallel jobs, | |
* Deallocate a collection while it is being used, | |
* Forget to deallocate a collection after all jobs complete, | |
* And more... | |
We want it to be impossible to shoot yourself in the foot with multi-threading. | |
// ###################################################################### | |
// | |
// Make it run faster!!! | |
// | |
// ###################################################################### | |
C# job compiler. | |
Subset of .NET IL => highly optimized native code through LLVM. | |
* No virtual functions | |
* No reference types | |
* No GC | |
[ComputeJobOptimization] | |
struct MyJob : IJob {} | |
// Don't care about precision | |
[ComputeJobOptimization(Accuracy.Med, Support.Relaxed)] | |
struct MyJob : IJob {} | |
New math library modelled after HLSL. | |
float, float2, float3, float4 | |
int, int2, int3, int4 | |
math.min, math.lerp, math.saturate, math.rsqrt | |
C# job compiler uses it to generate SIMD code. | |
// ###################################################################### | |
// | |
// When? | |
// | |
// ###################################################################### | |
Stages: | |
1. C# Job System -> 2017.3 | |
2. Component System -> just a project | |
3. Math Library -> just a project | |
4. C# Job Сompiler -> 2018.x | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment