こんにちは、Unityエンジニアのオオバです。
そろそろUnityのJobSystemをやらなきゃという思いにかられ、少しずつ始めてみようと思います。
実際JobSystem
やECS
を使わなくてもゲーム自体は作れると思いますが、それらを使うことで、浮いたリソースがクオリティアップにつながるのであれば、やらない手はないかなという思いです。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
準備物
PackageManager経由でJobsパッケージをインストールしておきます。この時Mathematicsパッケージがが必須なので、ついでにインストールしておきます。またあとでBurstCompilerの検証もするためBurstもインストールしておきます。
JobSystem使う上でインポートしているパッケージ一覧(不要なものもある) · GitHub
とりあえず、こんな感じのmanifest.jsonになっています。
IJobインターフェースを使って始める
まずはショートコードから始めるために、公式リファレンスのサンプルを参考にしてみます。
Unity - Scripting API: IJob
いくつかジョブを作る方法があるようですが、もっともシンプルであろうIJobインターフェースを使った方法から始めます。
struct VelocityJob : IJob
{
[ReadOnly]
public NativeArray velocity;
public NativeArray position;
public float deltaTime;
///
/// ジョブの処理内容
///
public void Execute()
{
for (var i = 0; i < position.Length; i++)
{
position[i] = position[i] + velocity[i] * deltaTime;
}
}
}
このように構造体にIJobインターフェースを実装してジョブ(このサンプルではVelocityJob)を定義します。
IJobインターフェースに定義されているのはExecute
関数です。Execute内にジョブの処理を書きます。またジョブ実行時に自動で呼ばれます。
バッファにNativeArray型を使用
public NativeArray velocity;
ジョブ用のバッファはGCを発生させないようにアンマネージドなNativeArray型
を使用します。
アンマネージドメモリ領域を確保している変数なので最終的に使用し終えたらDisposeする必要があります。
使い終わったらDispose
velocity.Dispose();
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# Jo…
Disposeし忘れた場合、Unity Editor上ではエラーを吐いてくれますが、実機では無視されてしまいメモリリーク状態になってしまうので注意が必要そうです。
プロファイラで確認してみると、WorkerThread(Unityが予め用意しているスレッド)に処理が分散されていることがわかります。
ここまでのソースコードはコチラ
ApplyVelocitySample.cs · GitHub
ここまでのまとめ
- ジョブ定義にはIJobインターフェースを実装する(他にもインターフェースはあります)
- NativeArrayでバッファを定義する(NativeArray以外の型もある)
- Blittable型のみジョブ内では使用可能
- NativeArrayは使用後はDispose
- NativeArrayメモリリークエラー出力はUnityエディタ上のみ
ここからは実際にJobSystem使ってみてパフォーマンス的にどうなん?といったところを確認していきます。
早くなっているのか?
先のプロファイラを見てると、
- Update関数処理時間 : 2.55ms
- WorkerThreadにおけるジョブの処理時間 : 1.52ms
約60%のリソースがMainThreadからWorkerThreadに移っていることがわかります。
果たしてこれが、どのくらい全体のパフォーマンスアップに繋がっているのか確認したいところです。
ということで、ジョブ使用 / 未使用をフラグで持たせてプロファイラで確認してみます。
結果から見ると、今回のサンプルではあくまでMainThreadからWorkerThreadに処理を分散させる時があり(毎フレームではない)、MainThreadのUpdate内の処理時間自体にあまり変化はありませんでした。
ただしジョブ未使用時はMainThreadをフルで使っていますが、JobSystemを使用することでWorkerThreadに仕事を振ることができた分、端末の高熱化が多少軽減されそうではあります(未検証)。
ここまでのソースコードはコチラ
ジョブの使用/未使用を切り替えてみた · GitHub
ここまでのまとめ
- 今回のサンプルの場合、JobSystemにおける大きなパフォーマンスアップは無い
- MainThreadからWorkerThreadに仕事を振ることで、MainThreadがフルで使われない事に意義がありそう
BurstCompilerの力
そういえばJobSystemにはBurstCompilerが使えたな〜ということを思い出したので、検証してみました。
BurstCompilerはJobSystemのExecute内を高速化する特殊なコンパイラです。
BurstCompilerを使うためには、以下のようにJob定義の構造体に[ComputeJobOptimizationAttribute]
という属性を記述するだけです。
[ComputeJobOptimizationAttribute]
struct VelocityJob : IJob
{
[ReadOnly]
public NativeArray velocity;
public NativeArray position;
BurstCompilerの結果
BurstCompiler未使用状態
BurstCompiler使用状態
という結果でBurstCompilerを使うことで、ジョブの処理が約44倍高速化しました。
これにはびっくり。
もちろんBurstCompilerを使うには制限があって限定的なテクニックかもしれませんが、できるだけ使えるように実装を努力したくなる数値です。
BurstCompierの使用制限
【CEDEC2018】CPUを使い切れ! Entity Component System(通称ECS) が切り開く新しいプログラミング
- 基本的に差し替え可能なコードで使用可能
- static変数使用不可
ここまでのまとめ
- BurstCompilerにはできるだけ対応したい。それだけのメリットがある。
まとめ
IJobインターフェースだけで、長くなってきたので、一旦ここでJobSystemをとりあえずやってみる序はおしまいです。ジョブで処理をさせるには、今までの実装の考え方を180度切り替えて設計する必要がありそうです(クラスが使えないというのは大きい...)。
とりあえず、IJob以外のジョブも一通り触ってみて、適切なジョブの使い方を模索していこうと思います。
今回検証してよかったのは、BurstCompilerはとても高速だということを実感できたことです。
がんばってBurstしていくぞ!
参考
- 【Unity】C# Job Systemを自分なりに解説してみる - テラシュールブログ
- 【Unity】C# JobSystemで大量のドカベンロゴをアニメーションさせてみた - Qiita
- 【CEDEC2017】C#JobSystem を使った Unity流マルチスレッドプログラミング
- 【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# Jo…
- 【CEDEC2018】CPUを使い切れ! Entity Component System(通称ECS) が切り開く新しいプログラミング
この記事が気に入ったらフォローしよう