plan-4ging

2026/03/10

C# Job System とは

マルチスレッド処理用のシステム

  • 通常の Unity コードはメインスレッド上でしか動かないが、Job System を使うことで複数ワーカースレッド上で処理を並列実行できる
  • パフォーマンス改善につながる(フレームレートの大幅な向上など)
【Job System なし】
Main Thread:Update → 重い処理(全部ここで実行) → 描画
 → 重い処理が Main Thread をブロック(フレーム落ち発生)

【Job System あり】
Main Thread:Update → Job をスケジュール → 描画
 + Worker Thread 1:重い処理A ─┐
 + Worker Thread 2:重い処理B ─┤ → 並列実行
 + Worker Thread 3:重い処理C ─┘
  → 重い処理から Main Thread が解放される(フレーム落ち回避)

Job System / Burst の関係

組み合わせることで最大の効果を発揮する

  • Job System:処理を複数スレッドに「分散」させる(並列化)
  • Burst Compiler:コード自体を CPU が理解しやすいネイティブコードに「変換」する(高速化)

Job の種類

種類用途
IJob1つの独立したタスクを実行
IJobParallelFor大量のデータ(配列)を分割して並列処理
IJobParallelForTransform大量の Transform の位置・回転を並列更新

IJob(単一タスク)

1つの大きな計算をバックグラウンドに逃がしたい時に使用

// struct として定義
public struct SimpleJob : IJob
{
    public float Multiplier;
    public NativeArray<float> Results;

    public void Execute()
    {
        for (int i = 0; i < Results.Length; i++)
            Results[i] *= Multiplier;
    }
}

IJobParallelFor

大量のデータを複数スレッドで並列処理する場合に使う

[BurstCompile] // Burst の最適化対象になる
public struct ParallelJob : IJobParallelFor
{
    [ReadOnly] public NativeArray<float> Input;  // 読み込み専用
    public NativeArray<float> Output;

    public void Execute(int index)
    {
        Output[index] = Input[index] * Input[index];
    }
}

..
// Schedule の第2引数:バッチサイズ(1スレッドあたりの処理件数)
JobHandle handle = job.Schedule(count, 64);
handle.Complete();

ParallelForTransform

Transform に対して動作するように特別に設計されている TransformAccessArrayに渡されたすべてのTransformの位置・回転・スケールに対して同じ独立した処理を行う

[BurstCompile] // Burst の最適化対象になる
public struct MoveJob : IJobParallelForTransform
{
    public float DeltaTime;
    public void Execute(int index, TransformAccess transform)
    {
        transform.position += Vector3.up * DeltaTime;
    }
}

Job の依存関係管理

複数の Job を連鎖させる場合、依存関係をJobHandleで管理する

void Start()
{
    var data = new NativeArray<float>(1000, Allocator.TempJob);

    // Job A:データを生成
    var jobA = new GenerateJob { Data = data };
    JobHandle handleA = jobA.Schedule();

    // Job B:ジョブAの結果を使って処理(handleA を依存として渡す)
    var jobB = new ProcessJob { Data = data };
    JobHandle handleB = jobB.Schedule(handleA); // A完了後にBを実行

    // Job C,D:Bの後に並列実行
    var jobC = new OutputJobC { Data = data };
    var jobD = new OutputJobD { Data = data };
    JobHandle handleC = jobC.Schedule(handleB);
    JobHandle handleD = jobD.Schedule(handleB);

    // 複数の JobHandle をまとめて待つ
    JobHandle.CombineDependencies(handleC, handleD).Complete();

    data.Dispose();
}

NativeContainer(NativeArray)と Allocator の種類

Job 内でデータをやり取りするには、GC 管理外のNativeContainerNativeArrayなど)が必要 ネイティブメモリへの比較的安全な C# ラッパーを提供するマネージドな値型「NativeContainerNativeArray)」を使用する

  • Job がメインスレッドのデータのコピーではなく、共有データにアクセスできるようにする
  • GC の管理外のネイティブメモリに確保されるため GC が発生しない
// 通常の C# 配列 → GC の管理下・Job に渡せない
float[] managed = new float[1000];

// NativeArray → GC 管理外・Job に渡せる
NativeArray<float> native = new NativeArray<float>(1000, Allocator.TempJob);

NativeContainerNativeArray)を使用する際、必要なメモリ割り当てのタイプ(Allocator)を指定する必要がある

Allocator速度寿命ジョブに渡せるかDispose
Allocator.Temp最速1フレーム以内不可不要(自動解放)
Allocator.TempJob中間4フレーム以内必須
Allocator.Persistent最遅無制限必須

注意点・使い所

注意点

// NG①:Complete() 前に NativeArray にアクセスする
var results = new NativeArray<float>(10, Allocator.TempJob);
JobHandle handle = job.Schedule();
Debug.Log(results[0]); // エラー:Job 実行中

// OK①:Complete() 後にアクセスする
var results = new NativeArray<float>(10, Allocator.TempJob);
JobHandle handle = job.Schedule();
handle.Complete();          // Job 完了を待つ
Debug.Log(results[0]);      // 完了後なので安全にアクセスできる
results.Dispose();

// NG②:NativeArray の Dispose 忘れ
var array = new NativeArray<float>(1000, Allocator.TempJob);
job.Schedule().Complete();
// Dispose がない → メモリリーク

// OK②:try / finally で確実に Dispose
var array = new NativeArray<float>(1000, Allocator.TempJob);
try
{
    job.Schedule().Complete();
}
finally
{
    array.Dispose();
}

使い所

向いている処理
 ├ 大量のオブジェクトの位置・物理計算
 ├ パスファインディング・AI の経路計算
 ├ 地形・メッシュの動的生成
 ├ 大量のパーティクルのシミュレーション
 └ 画像・テクスチャの処理

向いていない処理
 ├ Unity の API を呼ぶ処理(GameObject・Transform の直接操作など)
 ├ 件数が少ない処理(スレッド管理コストの方が大きくなる)
 └ 処理の依存関係が複雑でジョブ分割が難しい場合

参照