sunagimoブログ

主にUnityに関する技術を取り上げます

【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();
    }
}



ジョブ未使用
f:id:sunagimo_app:20190317030748p:plain


ジョブ使用
f:id:sunagimo_app:20190317030852p:plain


ジョブ使用時に他のワーカースレッドに差し込まれる


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();
    }
}



ジョブ未使用
f:id:sunagimo_app:20190317031315p:plain


ジョブ使用
f:id:sunagimo_app:20190317031331p:plain

複数のワーカースレッドが差し込まれる

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();
    }
}



ジョブ未使用
f:id:sunagimo_app:20190317040922p:plain


ジョブ使用
f:id:sunagimo_app:20190317040933p:plain


あれ、なぜかジョブ使用すると遅くなってる…
計測の仕方が悪かったのかもしれません。



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;
        }
    }
}


バーストコンパイラ使用時
f:id:sunagimo_app:20190317041711p:plain

処理時間が半分ぐらいになりました。
ここまでパフォーマンスが向上するとは…



まとめ

大量のデータ、オブジェクトを処理する際はとても便利な機能だと思います。
普段はそこまで使う機会はないかと思いますが、いつか必要なときが来るかと思います。




参考にさせていただいたサイト様

tsubakit1.hateblo.jp

www.shibuya24.info