はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】IJobParallelForTransformを初めて使ってみる

はじめに

今回はTransformに対して動作するようなParallelFor jobについて取り上げたいと思います。

f:id:hanaaaaaachiru:20220123213840g:plain
今回実装するもの

また用語の整理をさせていただくとこんな感じ。
ParallelForTransformジョブIJobParallelForTransformインターフェースを実装するジョブの総称
ParallelForジョブIJobParallelForインターフェースを実装する構造体の総称

docs.unity3d.com

stephenhodgson.github.io

その前に

前回書いた記事を見た後にこちらの記事を見ていただく方が分かりやすいかもしれません。

www.hanachiru-blog.com

また仕組みについては触れないのであしからず。

環境

Unity 2020.3.18f1
Burst 1.2.0-preview.11
Job 0.2.1-preview.3

IJobParallelForTransformを利用

public class ParallelForTransformTest : MonoBehaviour
{
    // TransformにアクセスできるIJobParallelFor
    // https://docs.unity3d.com/ja/current/ScriptReference/Jobs.IJobParallelForTransform.html
    private struct MyParallelForTransformJob : IJobParallelForTransform
    {
        [ReadOnly]
        public float time;
        
        public void Execute(int index, TransformAccess transform)
        {
            var distance = Vector3.Distance(transform.position, Vector3.zero);
            transform.localPosition += Vector3.up * math.sin(time * 3f + distance * 0.2f);
        }
    }

    [SerializeField] private GameObject prefab;

    // 対象のTransformの情報を格納しておく
    // 構造体,ParallelForTransformジョブをスケジュールする際に必要になる
    private TransformAccessArray _transformAccessArray;
    
    private void Start()
    {
        // Cubeをたくさん敷き詰める
        var size = 35;
        var list = new List<Transform>();

        for (var z = -size; z <= size; z++) {
            for (var x = -size; x <= size; x++)
            {
                var cube = Instantiate(prefab);
                cube.transform.position = new Vector3 (x * 1.1f, 0, z * 1.1f);
                list.Add(cube.transform);
            }
        }
        Debug.Log("Cube Count : " + list.Count);

        // https://docs.unity3d.com/ja/2018.4/ScriptReference/Jobs.TransformAccessArray.html
        _transformAccessArray = new TransformAccessArray(list.ToArray());
    }

    private void OnDestroy()
    {
        _transformAccessArray.Dispose();
    }

    private void Update()
    {
        var job = new MyParallelForTransformJob
        {
            time = Time.time,
        };

        // ジョブをスケジュールする
        // https://stephenhodgson.github.io/UnityCsReference/api/UnityEngine.Jobs.IJobParallelForTransformExtensions.html
        var handler = job.Schedule(_transformAccessArray);
        
        // 何かしらの処理をメインスレッドでしても良い
        //Thread.Sleep(10);
        
        handler.Complete();
    }
}

TransformAccessArrayを使うぐらいで、基本的にIJobParallelForの際と変わりません。

またProfilerで確認すると確かにマルチスレッドで動作しているようです。

f:id:hanaaaaaachiru:20220123214637p:plain
プロファイラー

比較

Editor上で、10秒間0.2秒毎に算出した平均FPSの結果が以下になります。

Main Threadのみ IJobParallelForTransform IJobParallelForTransform + Burst
31.75824 34.74327 35.0083

思ったより微妙な差になりましたが、Main Threadのみ < IJobParallelForTransform < IJobParallelForTransform + Burst という結果になりました。

コードは以下の通り。

メインスレッドのみ

private void Update()
{
    for (var i = 0; i < _transformAccessArray.length; i++)
    {
        var distance = Vector3.Distance(_transformAccessArray[i].position, Vector3.zero);
        _transformAccessArray[i].localPosition += Vector3.up * math.sin(Time.time * 3f + distance * 0.2f);
    }
}

Burst Compiler

[BurstCompile]
private struct MyParallelForTransformJob : IJobParallelForTransform
{
    [ReadOnly]
    public float time;
        
    public void Execute(int index, TransformAccess transform)
    {
        var distance = Vector3.Distance(transform.position, Vector3.zero);
        transform.localPosition += Vector3.up * math.sin(time * 3f + distance * 0.2f);
    }
}
f:id:hanaaaaaachiru:20220123222052p:plain
Burstの設定

さいごに

Youtubeにめちゃくちゃ分かりやすい動画があったので共有しておきます。
www.youtube.com