Upload
unitytechnologiesjapan
View
1.784
Download
0
Embed Size (px)
Citation preview
• 従来のマルチスレッドプログラミング • C# Job System の概要
• Let’s read codes. (コードを読む) • Let’s make a mistake. (間違ってみる) • Let’s try “C# Job Compiler” (コンパイラを体験) • Let’s implement. (実装してみる)
• まとめ
アジェンダ
• レースコンディション対策が嫌だ • 面倒臭い • コードが汚い、可読性が低い • 間違っても気づきにくい
MTPのここが嫌だ その1
Aが使うよ
Aが使うよBが使うよ
A
AB
?
write
write read
• Data Oriented Programming • データとビヘイビア(振舞い)の分離 • struct(構造体)コンポーネントの導入
• Job Component System の用意 • 簡潔に書けるようにマネージャーを用意
特徴1 簡潔に書ける
• GCをいかにさせないか • NativeArrayの導入
• 以下の感じで確保
• 以下の感じで解放(自分で)
特徴2 GCフリー
var src = new NativeArray<float>(500, Allocator.Temp);
src.Dispose();
要素数 アロケーターの種類 ↓ ↓
• 他のNativeArrayファミリー
特徴2 GCフリー
struct NativeArray<Value> // 配列 struct NativeList<Value> // リスト。追加削除が容易 struct NativeSlice<Value> // 一部を切り取れる struct NativeHashmap<Key, Value> // Dictionary「 struct NativeMultiHashmap<Key, Value> //複数Dictionary
• C#→[Mono]→IL→[C# Job Compiler]→内部的な Domain Model →[最適化]→[LLVM]→実行形式
• 10倍〜20倍高速になる • 電池消費の軽減 • Why faster?
• SIMD命令の有効利用 • 正確さとパフォーマンスのトレードオフ
特徴4 高速な新コンパイラ
• IJob~でジョブを定義 • Execute にジョブの中身を書く • Schedule でジョブを開始 • Complete でジョブ終了確認 • 変数はNativeArray系を使い、自力でDispose
コーディング基本まとめ
• IJob • 1つのスレッドでジョブを回す
• IJobParallelFor • 複数のスレッドでジョブを回す
• IJobParallelForTransform • Transformにアクセスが可能
コーディング基本まとめ
public void Execute() {}
public void Execute(int i) {}
public void Execute(int i, TransformAccess transform){}
• マルチスレッドプログラミングは間違えやすい • ちょっとした見落としはしてしまう
• Unityは落ちることなくエラーが教えてくれる • CTO Joachim「Unityは「Sandbox(=砂場)」である」
• 砂場では間違っていい。正解に導いてくれれれば。
エラーまとめ
• 一文付け足すだけ • [ComputeJobOptimizationAttribute(Accuracy
.Med, Support.Relaxed)] • Accuracy は計算の精度
• 新しいmathライブラリ
C# Job Compiler
• float1, float2, float3, float4, • half1, half2, half3, half4 • int1, int2, int3, int4 • math.abs • math.min • math.max • math.pow • math.lerp • math.clamp
• math.saturate • math.select // 条件分岐 • math.rcp // 逆数 • math.sign • math.rsqrt // sqrtの逆数 • math.any • math.all • math.sincos
新mathライブラリ
public class RotatorOldUpdate : MonoBehaviour { [SerializeField] float m_Speed; public float speed { get { return m_Speed; } set { m_Speed = value; } }
void Update () { transform.rotation = transform.rotation * Quaternion.AngleAxis (m_Speed * Time.deltaTime, Vector3.up); } }
• STEP1:データレイアウトの最適化 • GameObjectごとにするのはやめる • データをシーケンシャルにする
• キャッシュ化する • forループでGetComponentとかしなくてよくなる
Job Component System実装まとめ
public class RotatorOldUpdate : MonoBehaviour { [SerializeField] float m_Speed; public float speed { get { return m_Speed; } set { m_Speed = value; } }
void Update () { transform.rotation = transform.rotation * Quaternion.AngleAxis (m_Speed * Time.deltaTime, Vector3.up); } }
class RotatorManagerMainThread : ScriptBehaviourManager { List<Transform> m_Transforms; NativeList<float> m_Speeds; : protected override void OnUpdate() { base.OnUpdate (); float deltaTime = Time.deltaTime; NativeArray<float> speeds = m_Speeds; for (int i = 0; i != m_Transforms.Count; i++) { var transform = m_Transforms [i]; transform.rotation = transform.rotation * Quaternion.AngleAxis (speeds[i] * deltaTime, Vector3.up); } } : : } public class RotatorWithManagerMainThread : ScriptBehaviour { : (たくさんの実装) : }
• STEP2: Job化 • List<Transform> → TransformAccessArray • IJobParallelForTransform継承したジョブ
• Execute(int index, TransformAccess transform)の実装
Job Component System実装まとめ
class RotatorManagerMainThread : ScriptBehaviourManager { List<Transform> m_Transforms; NativeList<float> m_Speeds; : protected override void OnUpdate() { base.OnUpdate (); float deltaTime = Time.deltaTime; NativeArray<float> speeds = m_Speeds; for (int i = 0; i != m_Transforms.Count; i++) { var transform = m_Transforms [i]; transform.rotation = transform.rotation * Quaternion.AngleAxis (speeds[i] * deltaTime, Vector3.up); } } : : } public class RotatorWithManagerMainThread : ScriptBehaviour { : (たくさんの実装) : }
class RotatorManager : ScriptBehaviourManager { TransformAccessArray m_Transforms; NativeList<float> m_Speeds; JobHandle m_Job; : protected override void OnUpdate() { base.OnUpdate (); m_Job.Complete (); var jobData = new RotatorJob(); jobData.speeds = m_Speeds; jobData.deltaTime = Time.deltaTime; m_Job = jobData.Schedule (m_Transforms); }
struct RotatorJob : IJobParallelForTransform { [ReadOnly] public NativeArray<float> speeds; public float deltaTime; public void Execute(int index, TransformAccess transform) { transform.rotation = transform.rotation * Quaternion.AngleAxis (speeds[index] * deltaTime, Vector3.up); } } }
public class RotatorWithManager : ScriptBehaviour { : (たくさんの実装) : }
• STEP3: データからビヘイビアを分離する • ジョブで使用するデータを分離する • InjectTuplesの導入
• Tuples が付加した配列はindexが同期する • ComponentSystemから継承させる
• マネージャーの仕事を任せる
Job Component System実装まとめ
public class RotationSpeedComponent : ScriptBehaviour { public float speed; }
public class RotatingSystem : ComponentSystem { [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); } } }
• STEP4: データのstruct化 • MonoBehaviour継承 → IComponentData継承
• struct化 • ComponentSystemからの継承でお手軽マネー
ジャー • ComponentArray → ComponentDataArray
Job Component System実装まとめ
public class RotationSpeedComponent : ScriptBehaviour { public float speed; }
public class RotatingSystem : ComponentSystem { [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); } } }
[Serializable] public struct RotationSpeed : IComponentData { public float speed; public RotationSpeed (float speed) { this.speed = speed; } }
public class RotationSpeedDataComponent : ComponentDataWrapper<RotationSpeed> { }
public class RotatingDataSystem : ComponentSystem { [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); } } }
• STEP5: ジョブ実装 と 依存性解決 • IJobParallelForTransformを継承したstruct
• Execute で Transformが使える • ComponentSystem → JobComponentSystem
• GetDependency()で依存性の自動解決
Job Component System実装まとめ
[Serializable] public struct RotationSpeed : IComponentData { public float speed; public RotationSpeed (float speed) { this.speed = speed; } }
public class RotationSpeedDataComponent : ComponentDataWrapper<RotationSpeed> { }
public class RotatingDataSystem : ComponentSystem { [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); } } }
[Serializable] public struct RotationSpeed : IComponentData { public float speed; public RotationSpeed (float speed) { this.speed = speed; } }
public class RotationSpeedDataComponent : ComponentDataWrapper<RotationSpeed> { }
public class SystemRotator : JobComponentSystem { [InjectTuples] public TransformAccessArray m_Transforms; [InjectTuples] public ComponentDataArray<RotationSpeed> m_Rotators; override protected 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); } } }
• データ構造はstructのみ (class はNG) • .NETやUnity のAPIはジョブ内では(基本的に)使えない • 何でもかんでも早くなるわけではない
• 算術系が早くなる、と考えるのが正解 • 相互の距離の計算とか • 敵AIの思考ルーチンとか
C# Job System 注意点
• STEP1 C# Job system • Unity 2017.3 or 2018.X
• STEP2 Component system • STEP3 math library • STEP4 C# Job Compiler
リリース予定