😊

〜UnityのDOTSのドキュメントに触れてみる〜その2

に公開

前回の続きで、Unity公式のDOSTのドキュメントを少しずつ読みながら実装していこうと思います。

Burstコンパイラについて

BurstコンパイラはC#JobSystem向けのコンパイラとの事で、前回実践してみた
Jobに対してコンパイラの最適化を行ってくれるものです。

実装方法

MultiplicationJob.cs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// <summary>
/// 2つの配列の要素を乗算するジョブ(IJobを継承してジョブの構造体にする)
/// </summary>
[BurstCompile] <-これを付けるだけでOK
struct MultiplicationJob : IJob
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Jobの宣言の上に[BurstCompile]を付けるだけでBurstコンパイラを使用できます。

検証

通常のJobとBurstのJobがあるので、データ数を1000000に設定して乗算処理の実行速度の検証をしてみた所この小ささのJobでもこれぐらい差があるので、ゲームに実装するJobを作成するとかなりの差が出てくるのではないかと思ってます。

全体コード

下記3ファイルを実装してBurstExampleをゲームオブジェクトにアタッチして実行すると実行速度を検証できます。

Burstを実装したJobのソースコード

BurstMultiplicationJob.cs

using Unity.Collections;
using Unity.Jobs;
using Unity.Burst;

namespace DOTS.Jobs
{
    /// <summary>
    /// 2つの配列の要素を乗算するジョブ(IJobを継承してジョブの構造体にする)
    /// </summary>
    [BurstCompile] // Burstコンパイラでコンパイルすることを指定
    struct BurstMultiplicationJob : IJob
    {   
        // ガベージコレクションされないネイティブ配列で入力と出力リストを用意
        public NativeArray<float> Input;
        public NativeArray<float> Output;

        /// <summary>
        /// 実行関数
        /// </summary>
        public void Execute()
        {
            // 入力リストで出力リストを乗算する
            for (int index = 0; index < Input.Length; index++)
            {
                Output[index] *= Input[index];
            }
        }
    }
}

通常Jobのソースコード

MultiplicationJob.cs
using Unity.Collections;
using Unity.Jobs;

namespace DOTS.Jobs
{
    /// <summary>
    /// 2つの配列の要素を乗算するジョブ(IJobを継承してジョブの構造体にする)
    /// </summary>
    struct MultiplicationJob : IJob
    {
        // ガベージコレクションされないネイティブ配列で入力と出力リストを用意
        public NativeArray<float> Input;
        public NativeArray<float> Output;

        /// <summary>
        /// 実行関数
        /// </summary>
        public void Execute()
        {
            // 入力リストで出力リストを乗算する
            for (int index = 0; index < Input.Length; index++)
            {
                Output[index] *= Input[index];
            }
        }
    }
}

実行速度検証ソースコード

BurstExample.cs
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using DOTS.Jobs;

namespace DOTS.Example
{
    /// <summary>
    /// Jobの使用方法
    /// </summary>
    public class BurstExample : MonoBehaviour
    {
        [Header("テストデータの個数")]
        [SerializeField]
        int dataNum;
    
        /// <summary>
        /// 初期化
        /// </summary>
        void Start()
        {
            // 入力と出力データを100万配列用意
            NativeArray<float> input = new NativeArray<float>(dataNum, Allocator.Persistent);
            NativeArray<float> output = new NativeArray<float>(dataNum, Allocator.Persistent);

            // テストデータの初期化
            for (int index = 0; index < dataNum; index++)
            {
                input[index] = index + 1;
                output[index] = 10;
            }

            // ジョブを作成
            Debug.Log("Job作成");
            BurstMultiplicationJob bJob = new BurstMultiplicationJob
            {
                Input = input,
                Output = output
            };
            MultiplicationJob nJob = new MultiplicationJob
            {
                Input = input,
                Output = output
            };

            Debug.Log("通常Jobを実行");
            
            // 実行時間を計測開始
            System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
            
            // job.Schedule()でjobを実行してJobHandleを保持
            JobHandle handle = nJob.Schedule();
        
            // handle.Complete()でjobの完了を待つ
            handle.Complete();
            
            // 実行時間を計測終了
            stopwatch.Stop();
            
            // 結果の表示
            Debug.Log($"通常Job実行完了、実行時間: {stopwatch.ElapsedMilliseconds}ms");
            
            Debug.Log("BurstJobを実行");
            
            // 実行時間を計測開始
            stopwatch = System.Diagnostics.Stopwatch.StartNew();
            
            // job.Schedule()でjobを実行してJobHandleを保持
            handle = bJob.Schedule();
        
            // handle.Complete()でjobの完了を待つ
            handle.Complete();
            
            // 実行時間を計測終了
            stopwatch.Stop();
            
            // 結果の表示
            Debug.Log($"通常Job実行完了、実行時間: {stopwatch.ElapsedMilliseconds}ms");

            // ネイティブ配列の解放(必須)
            Debug.Log("メモリ解放");
            input.Dispose();
            output.Dispose();
        }
    }
}

あとがき

BurstコンパイラについてはJob専用+実装方法が簡単だったので短めになりました、Job専用とはいえかなり処理速度が上がったので簡単なロジック、Jobで実装できそうなロジックなどはBurstコンパイラのJobで実装すると効率が良さそうでした。
ドキュメントから次回はECSについてです。
(ECSについては大きいので分割予定)

参考にした記事

ファースト・スクラッチTech Blog

Discussion