Unity DOTS 入門 (4) - C# Job System
DOTSの構成要素の1つである「C# Job System」を解説します。
・Unity 2019.3.14.f1
・Jobs 0.2.10
1. C# Job System
「C# Job System」は、並列処理を行うための機能です。実行の順番やタイミングを気にしせず、ジョブを実行するだけで、CPUコアをフル活用することができます。
特徴は、次のとおりです。
・コードを簡潔の書ける
・GCフリー
・安全
・高速
「メインスレッド」で全ての処理を実行するには重い時、処理を複数に細かい処理に分割した「Job」を作成し、「Jobキュー」に追加(スケジュール)します。「ワーカースレッド」は、「Jobキュー」から「Job」を取り出して実行します。
この時、「C# Job System」は依存関係を管理しているため、Jobが適切な順序で実行されます。たとえば、JobBがJobAに依存している場合、JobAが完了するまでJobBが実行されないことが保証されます。
2 NativeContainer
「NativeContainer」は、C#が提供しているマネージドなContainer(List、Dictionaryなど)とは異なり、アンマネージドなContainerになります。GCで管理されないので、自分でDispose()でメモリを開放する必要があります。
特徴は、次のとおりです。
・メモリ割当タイプ(Allocator)を自身で決める
・使用後はDispose()でメモリを開放する必要がある
・構造体のみ(クラスはNG)
・要素数を増やせない
「NativeContainer」の種類は、次のとおりです。
・NativeArray<Value> : 配列
・NativeSlice<Value> : NativeArrayから一部切り取る
・NativeList<Value> : List
・NativeHashMap<Key, Value> : Dictionary
・NativeMultiHashMap<Key, Value> : キー毎に複数の値を持つDictionary
・NativeQueue<Value> : 先入れ先出し(FIFO)の待ち行列
「C# Job System」では「NativeContainer」で、Jobとメインスレッド間のデータ共有が可能です。Jobとメインスレッドのデータの受け渡しに使います。
3. Jobs パッケージのインストール
Jobs パッケージのインストール手順は、次のとおりです。
(1) メニュー「Window → Package Manager」でPackage Managerを開く。
(2) ウィンドウ上部の「Advances → show preview packages」をチェック。
(3) 「Jobs」パッケージ(0.2.10)を検索してインストール。
4. 単独のジョブの実行
空のゲームオブジェクト「CreateJob」を作成し、スクリプトのStart()で、単独のJobを実行します。
(1) Hierarchyウィンドウの「+ → Create Empty」で空のゲームオブジェクトを作成し、名前に「CreateJob」を指定。
(2) 「CreateJob」にスクリプト「CreateJob」を追加し、以下のように編集。
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
public class CreateJob : MonoBehaviour
{
// Jobの定義
private struct MyJob : IJob
{
public float a;
public NativeArray<float> result;
public void Execute()
{
result[0] = a;
}
}
void Start()
{
// NativeContainerの生成
NativeArray<float> resultArray = new NativeArray<float>(1, Allocator.TempJob);
// Jobの生成
MyJob myJob = new MyJob
{
a = 5f,
result = resultArray
};
// Jobの実行方法の指定
JobHandle handle = myJob.Schedule();
// ここで他のJobを実行
// Jobの完了を待つ
handle.Complete();
// 結果出力
Debug.Log("resultArray[0] = " + resultArray[0]);
// NativeContainerの破棄
resultArray.Dispose();
}
}
◎ NativeContainerの生成
NativeContainerのひとつ、「NativeArray<float>」を生成します。第1引数が「要素数」、第2引数が「メモリ割当タイプ」になります。
「メモリ割り当てタイプ」は、次のとおりです。
・Allocator.Temp : メモリの割当と開放が1フレーム以下で使用。
・Allocator.TempJob : メモリの割当と開放が4フレーム以内で使用。
・Allocator.Persistent : アプリの存続期間中、永続的な割当を行う。
今回は、「Allocator.Temp」を指定しています。
◎ Jobの定義
Jobを定義するには、はじめに、「IJob」を継承した構造体(struct)を準備します。次に、Jobの処理で使う変数をフィールドに準備します。最後に、Execute()でJobの処理を実装します。
Jobの処理では、.Net や Unity のAPIは基本的に使えません。主に「Unity.Mathematics」を使った算術演算を行います。
今回は、数値の代入のみ行っています。
◎ Jobの生成
「Job」は、「IJob」を継承した構造体(struct)として生成します。
「Job」のフィールドには、次の2つの型を利用できます。
・プリミティブ : int, float, bool等。
・NativeContainer : NativeArray、NativeSlice、NativeList等。
Jobとメインスレッド間でデータ共有できるのは、「NativeContainer」のみで、「プリミティブ型」は入力には使えますが、出力には使えません。
◎ Jobの実行方法の指定
Jobの実行方法の指定には、以下の3つが利用できます。
・Run() : ラムダ式をメインスレッドで実行。この場合、Jobの完了を待つ。
・Schedule() : ラムダ式を単一のJobとしてスケジュール。
・ScheduleParallel() : ラムダ式をチャンク単位で分割した複数のJobとしてスケジュール。
戻り値は、「JobHandle」です。スケジュールされたJobを操作するためのハンドルになります。
◎ JobHandleの操作
「JobHandle」のComplete()を呼ぶことで、Jobの完了を待ちます。
(3) 実行。
5. 複数のJobの実行
「myJob」を処理が完了した後に、「anotherJob」を実行するように、スケジューリングします。
(1) スクリプト「CreateJob」にJob「AnotherrJob」を追加します。
// 別のジョブ
private struct AnotherJob : IJob
{
public NativeArray<float> result;
public void Execute()
{
result[0] = result[0] + 1;
}
}
(2) スクリプト「CreateJob」のStart()を以下のように変更します。
void Start()
{
// NativeContainerの生成
NativeArray<float> resultArray = new NativeArray<float>(1, Allocator.TempJob);
// Jobの生成
MyJob myJob = new MyJob
{
a = 5f,
result = resultArray
};
// 別のJobの生成
AnotherJob anotherJob = new AnotherJob
{
result = resultArray
};
// Jobのスケジューリング
JobHandle handle = myJob.Schedule();
JobHandle anotherHandle = anotherJob.Schedule(handle);
// 別のJobの完了を待つ
anotherHandle.Complete();
// 結果出力
Debug.Log("resultArray[0] = " + resultArray[0]);
// NativeContainerの破棄
resultArray.Dispose();
}
◎ Jobのスケジューリング
「JobHandle」を別のジョブのSchedule()の引数として渡すことで、Jobが完了してから別のJobが実行されるようになります。
(3) 実行。
5の代入が完了してから1加算されていることが確認できます。
【おまけ】 Unity.Mathematics
今回は使っていませんが、Jobのラムダ式内の算術演算には、「Unity.Mathematics」を使います。
・float1, float2, float3, float4
・half1, half2, half3, half4
・int1, int2, int3, int4
・math.abs
・math.min
・math.max
・math.pow
・math.lerp
・math.clamp
・math.saturate
・math.select
・math.rcp
・math.sign
・math.rsqrt
・math.any
・math.all
・math.sincos
この記事が気に入ったらサポートをしてみませんか?