【Unity】C# Job Systemを使ってみる(Burst Compilerも)
C# Job Systemとは
・並列処理を行う機能
・仕事が割り振られていないCPUに割り当てて、複数で処理を行う
・バッテリーの消費が抑えられる
C# Job Systemの特徴
・データ構造はstructのみ
・.NETやUnityのAPIは使えない
・すべてが早くなるわけではない(距離計算、AI)
基本定義
IJob
一つのジョブを発行する
ジョブ未使用時と使用時を比較
<ジョブ未使用>
using System.Collections; using System.Collections.Generic; using UnityEngine; using Unity.Jobs; using Unity.Collections; public class JobSystemSample : MonoBehaviour { private int processCount = 1000000; void Update() { ExecuteSample(); } void ExecuteSample() { var result = new NativeArray<int>(processCount, Allocator.TempJob); var value1 = 10; var value2 = 20; for(var idx = 0; idx < result.Length; ++idx) { result[idx] = value1 + value2; } result.Dispose(); } }
<ジョブ使用>
using System.Collections; using System.Collections.Generic; using UnityEngine; using Unity.Jobs; using Unity.Collections; public class JobSystemSample : MonoBehaviour { struct SampleJob : IJob { public int value1; public int value2; public NativeArray<int> result; public void Execute() { for(var idx = 0; idx < result.Length; ++idx) { result[idx] = value1 + value2; } } } private int processCount = 1000000; void Update() { ExecuteSampleJob(); } void ExecuteSampleJob() { // バッファ生成。 var result = new NativeArray<int>(processCount, Allocator.TempJob); // ジョブ生成。 var sampleJob = new SampleJob(); sampleJob.value1 = 10; sampleJob.value2 = 20; sampleJob.result = result; // ジョブ実行。 var jobHandle = sampleJob.Schedule(); // ジョブ完了待機。 jobHandle.Complete(); // バッファの破棄。 result.Dispose(); } }
ジョブ未使用
ジョブ使用
ジョブ使用時に他のワーカースレッドに差し込まれる
IJobParalellFor
配列の各要素単位で並行して処理を行う
ジョブ未使用時と使用時を比較
<ジョブ未使用>
using System.Collections; using System.Collections.Generic; using UnityEngine; using Unity.Jobs; using Unity.Collections; public class JobSystemSample : MonoBehaviour { private int processCount = 100000; void Update() { ExecuteSampleParallel(); } void ExecuteSampleParallel() { // バッファ生成。 var result = new NativeArray<int>(processCount, Allocator.TempJob); var value1 = new NativeArray<int>(processCount, Allocator.TempJob); var value2 = new NativeArray<int>(processCount, Allocator.TempJob); for(var idx = 0; idx < value1.Length; ++idx) { value1[idx] = idx; } for(var idx = 0; idx < value2.Length; ++idx) { value2[idx] = idx; } for(var idx = 0; idx < result.Length; ++idx) { result[idx] = value1[idx] + value2[idx]; } // バッファの破棄。 value1.Dispose(); value2.Dispose(); result.Dispose(); } }
<ジョブ使用>
using System.Collections; using System.Collections.Generic; using UnityEngine; using Unity.Jobs; using Unity.Collections; public class JobSystemSample : MonoBehaviour { struct SampleParallelJob : IJobParallelFor { public NativeArray<int> value1; public NativeArray<int> value2; public NativeArray<int> result; public void Execute(int i) { result[i] = value1[i] + value2[i]; } } private int processCount = 100000; void Update() { ExecuteSampleParallelJob(); } void ExecuteSampleParallelJob() { // バッファ生成。 var result = new NativeArray<int>(processCount, Allocator.TempJob); var value1 = new NativeArray<int>(processCount, Allocator.TempJob); var value2 = new NativeArray<int>(processCount, Allocator.TempJob); for(var idx = 0; idx < value1.Length; ++idx) { value1[idx] = idx; } for(var idx = 0; idx < value2.Length; ++idx) { value2[idx] = idx; } // ジョブ生成。 var parallelJob = new SampleParallelJob(); parallelJob.value1 = value1; parallelJob.value2 = value2; parallelJob.result = result; // ジョブ実行。 var jobHandle = parallelJob.Schedule(result.Length, 1); // ジョブ完了待機。 jobHandle.Complete(); // バッファの破棄。 value1.Dispose(); value2.Dispose(); result.Dispose(); } }
ジョブ未使用
ジョブ使用
複数のワーカースレッドが差し込まれる
IJobParalellForTransform
複数のTransformに対して並行して処理を行う
ジョブ未使用時と使用時を比較
<ジョブ未使用>
using System.Collections; using System.Collections.Generic; using UnityEngine; using Unity.Jobs; using Unity.Collections; using UnityEngine.Jobs; public class JobSystemSample : MonoBehaviour { private int processCount = 10000; [SerializeField] private Transform prefab = null; private TransformAccessArray prefabTransformAccessArray; void Start() { CreatePrefab(); } void CreatePrefab() { var transforms = new Transform[processCount]; for(var idx = 0; idx < processCount; ++idx) { var prefabInstance = Instantiate(prefab); transforms[idx] = prefabInstance; } prefabTransformAccessArray = new TransformAccessArray(transforms); } void OnDestroy() { prefabTransformAccessArray.Dispose(); } void ExecuteSampleParallelTransform() { for(var idx = 0; idx < prefabTransformAccessArray.length; ++idx) { prefabTransformAccessArray[idx].localPosition = new Vector3(Random.Range(-15, 15), Random.Range(-15, 15), Random.Range(-15, 15)); } } void Update() { ExecuteSampleParallelTransform(); } }
<ジョブ使用>
using System.Collections; using System.Collections.Generic; using UnityEngine; using Unity.Jobs; using Unity.Collections; using UnityEngine.Jobs; public class JobSystemSample : MonoBehaviour { struct SampleParallelTransformJob : IJobParallelForTransform { public NativeArray<Vector3> positions; public void Execute(int idx, TransformAccess transform) { var pos = positions[idx]; transform.localPosition = pos; } } private int processCount = 10000; [SerializeField] private Transform prefab = null; private TransformAccessArray prefabTransformAccessArray; void Start() { CreatePrefab(); } void CreatePrefab() { var transforms = new Transform[processCount]; for(var idx = 0; idx < processCount; ++idx) { var prefabInstance = Instantiate(prefab); transforms[idx] = prefabInstance; } prefabTransformAccessArray = new TransformAccessArray(transforms); } void OnDestroy() { prefabTransformAccessArray.Dispose(); } void ExecuteSampleParallelTransformJob() { var positions = new NativeArray<Vector3>(prefabTransformAccessArray.length, Allocator.TempJob); for(var idx = 0; idx < prefabTransformAccessArray.length; ++idx) { positions[idx] = new Vector3(Random.Range(-15, 15), Random.Range(-15, 15), Random.Range(-15, 15)); } var sampleParallelTransformJob = new SampleParallelTransformJob(); sampleParallelTransformJob.positions = positions; var jobHandle = sampleParallelTransformJob.Schedule(prefabTransformAccessArray); jobHandle.Complete(); positions.Dispose(); } void Update() { ExecuteSampleParallelTransformJob(); } }
ジョブ未使用
ジョブ使用
あれ、なぜかジョブ使用すると遅くなってる…
計測の仕方が悪かったのかもしれません。
BurstCompiler
JobSystemやECSと合わせるとパフォーマンスが更に向上する機能
Package Managerより導入できます。
docs.unity3d.com
using Unity.Burst
[BurstCompile]のアトリビュートをつけるだけです。
using System.Collections; using System.Collections.Generic; using UnityEngine; using Unity.Jobs; using Unity.Collections; using UnityEngine.Jobs; using Unity.Burst; public class JobSystemSample : MonoBehaviour { [BurstCompile] struct SampleParallelTransformJob : IJobParallelForTransform { public NativeArray<Vector3> positions; public void Execute(int idx, TransformAccess transform) { var pos = positions[idx]; transform.localPosition = pos; } } }
バーストコンパイラ使用時
処理時間が半分ぐらいになりました。
ここまでパフォーマンスが向上するとは…
まとめ
大量のデータ、オブジェクトを処理する際はとても便利な機能だと思います。
普段はそこまで使う機会はないかと思いますが、いつか必要なときが来るかと思います。
参考にさせていただいたサイト様