〜UnityのDOTSのドキュメントに触れてみる〜その1
UnityDOTSというものが気になったので、Unity公式のDOSTのドキュメントを少しずつ読みながら実装していこうと思います。
公式のドキュメント+Google翻訳したドキュメントを使用して読み進めていきます。
UnityDOTSとは
DOTSとは、Unity6から導入されたオブジェクト指向->データ指向に移行する為のUnityの新しい基礎Data-Oriented Tech Stack
の略で、現状GameObjectと共存している様子だが、Unityが言うには最終的には完全に移行してしまうとの事、DOTSの構成は下記になっています。
- ECS(Entity Component System)
GameObject
とMonoBehaviour
に変わるもの、デザインパターン(MVVM/MVP/MVC)のように
Entity
とComponent
とSystem
でやる事が分割されているものが用意されている
(Entities
パッケージが必要) - C# Job System
マルチスレッドでの処理や1つの特定の処理を実行する時に使用、簡単にマルチスレッドのコードが書けたりする
(Jobs
パッケージが必要) - Burstコンパイラ
最適化されたコンパイルをしてくれる、従来のコンパイルよりも高速
(Burst
パッケージが必要)
ドキュメントを読む
ドキュメントを上から読んでいきます。
(基本的にGoogle翻訳したドキュメントで日本語で読んでいきます)
Jobシステム
最初にパフォーマンスの事についての記載がありますが、割愛します。
(何故パフォーマンスが出ないのか、メモリやガベージコレクションの事などの記載があります)
MonoBehaviourの更新はメインスレッドでのみ実行されるため、多くのUnityゲームではゲームロ ジックのすべてが1つのCPUコアで実行されることになります。
追加のコアを活用するには、手動で追加のスレッドを生成して管理することもできますが、安全かつ効率的に行うことは非常に困難です。
より簡単な代替手段として、UnityはC#ジョブシステムを提供します。
-ジョブシステムは、各コアごとに1つのワーカースレッドのプールを維持します。
ターゲットプラットフォーム。たとえば、Unityが8つのコアで実行される場合、1つのメインスレッ ドと7つのワーカースレッドが作成されます。
-ワーカースレッドはジョブと呼ばれる作業単位を実行します。ワーカースレッドがアイドル状態のと きは、ジョブキューから次に利用可能なジョブを取得して実行します。
-ジョブがワーカースレッドで実行を開始すると、完了するまで実行されます。(つまり、ジョブはプリエンプトされません。)
上記は日本語化したドキュメントのJobシステムの解説部分です。
自力でメインスレットを管理する事もできるようですが、Jobシステムを使うと効率良くマルチスレッドを実行してくれるようです。
ドキュメントのソースコード(コメント部分は追記しています)
using Unity.Collections;
using Unity.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];
}
}
}
解説
- Jobを使うには
struct MultiplicationJob : IJob
のようにIJob
を継承して使用します。
class
を使用しても良いですがstruct
にする事を推奨されています。
(今回はInputでOutputを乗算するJobを作成してます) -
NativeArray
なのはガベージコレクションを使わない事でパフォーマンスを向上させています。 -
public void Execute()
JobシステムではExecute
関数の実装が必須でExecute
でそのJobでしたい事を実装します。
(今回はInputでOutputを乗算しています、IJob以外のJobもあるようです)
動作テスト
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using DOTS.Jobs;
/// <summary>
/// Jobの使用方法
/// </summary>
public class JobExample : MonoBehaviour
{
[Header("テストデータの個数")]
[SerializeField]
int dataNum;
/// <summary>
/// 初期化
/// </summary>
void Start()
{
// 入力と出力データを100万配列用意
var input = new NativeArray<float>(dataNum, Allocator.Persistent);
var output = new NativeArray<float>(dataNum, Allocator.Persistent);
// テストデータの初期化
for (int index = 0; index < dataNum; index++)
{
input[index] = index + 1;
output[index] = 10;
}
// ジョブを作成
Debug.Log("Job作成");
MultiplicationJob job = new MultiplicationJob
{
Input = input,
Output = output
};
// job.Schedule()でjobを実行してJobHandleを保持
Debug.Log("Jobを実行");
JobHandle handle = job.Schedule();
// handle.Complete()でjobの完了を待つ
handle.Complete();
Debug.Log("Job実行完了");
// ネイティブ配列の解放(必須)
Debug.Log("メモリ解放");
input.Dispose();
output.Dispose();
}
}
使用方法解説
- 通常のクラスを作成するように作成
MultiplicationJob job = new MultiplicationJob
{
Input = input,
Output = output
};
-
JobHandle handle = job.Schedule();
でJobを実行して情報をJobHandle
で保持 -
handle.Complete();
でJobの完了を待つ - 最後に忘れずに
NativeArray
のメモリ解放を行う
input.Dispose();
output.Dispose();
IJob以外のJob
- IJobParallelFor:Execute(int index)
- IJobChunk:Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
- IJobEntity:Execute(ECSの型を必要な数だけ引数に取れる)
Allocatorについて
- Temp:1フレームで破棄される、次フレームには使用できない
- TempJob:4フレームで破棄される、4フレーム以内に使用する
- Persistent:永続的に使用される、Disposeするまで破棄されない
あとがき
ドキュメント内ではIJob
しか解説していなかったですが、一番使いそうなJobは並列実行できるとの事なのでIJobParallelFor
とECS
を使うのであればIJobEntity
だと思います。
IJob
に関しては何か決まった物を返すような処理に適しているのかなと思いました。
(役割的にはSystemとかModelが似ていると思います)
ドキュメントを見ると次はBurst
コンパイラについてになりそうです。
Discussion