Version: 2022.1
言語: 日本語
C# Job System の安全システム
ジョブの作成

NativeContainer

データをコピーする 安全システム による処理の欠点は、各コピー内のジョブの結果も単独化してしまうことです。この制限を解決するには、NativeContainer と呼ばれる共有メモリに結果を保存する必要があります。

NativeContainer はマネージ型で、安全な C# ラッパーをネイティブメモリに提供します。これには、アンマネージの割り当てへのポインターが含まれます。Unity C# Job System で使用する場合、NativeContainer を使用すると、ジョブはコピーを使って作業するのではなく、メインスレッドと共有するデータにアクセスできるようになります。

NativeContainer のタイプ

Unity には、NativeArray と呼ばれる NativeContainer が搭載されています。NativeArrayNativeSlice で操作して、NativeArray の特定の位置から特定の長さのサブセットを取得することもできます。

ノート:Entity Component System (ECS) パッケージは Unity.Collections 名前空間を拡張し、他のタイプの NativeContainer を含みます。

  • NativeList - サイズ変更可能な NativeArray
  • NativeHashMap - キーと値のペア
  • NativeMultiHashMap - 各キーに複数の値
  • NativeQueue - 先入れ先出し (FIFO) キュー

NativeContainer と安全システム

セーフティシステムはすべての NativeContainer タイプに組み込まれています。 NativeContainer へのすべての読み取りと書き込みを追跡します。

ノート: NativeContainer タイプのすべての安全チェック (例えば、範囲外チェック、割り当て解除チェック、競合状態チェック) は、Unity の エディター再生モード でのみ使用可能です。

この安全システムには DisposeSentinelAtomicSafetyHandle が含まれます。 DisposeSentinel はメモリリークを検出し、正しくメモリが解放されていない場合はエラーを返します。メモリリークエラーは、リークが発生してからかなり後に送信されます。

AtomicSafetyHandle を使用すると、コードの NativeContainer の所有権を移動します。例えば、2 つのスケジュールされたジョブが同じ NativeArray に書き込みを行なうと、安全システムは例外を発生させ、問題の原因と解決方法を説明する明確なエラーメッセージを示します。安全システムは、違反のジョブがスケジュールされたときにこの例外をスローします。

この場合、依存関係を持つジョブをスケジュールに組み込めます。最初のジョブは NativeContainer に書き込むことができ、実行が終了すると、次のジョブは同じ NativeContainer から安全に読み取り、または書き込みすることができます。読み取りと書き込みの制限は、メインスレッドからデータにアクセスするときにも適用されます。安全システムでは、複数のジョブが並列で同じデータから読み取ることができます。

デフォルトでは、NativeContainer へアクセスできるジョブは、読み取りと書き込みの両方のアクセスが可能です。この設定はパフォーマンスを遅くする可能性があります。C# Job System では、NativeContainer への書き込みアクセス権を持つジョブを、NativeContainer への書き込みアクセス権を持つ他のジョブと同時にスケジュールすることはできません。

ジョブが NativeContainer への書き込みを必要としない場合は、以下のように NativeContainer[ReadOnly] 属性を設定します。

[ReadOnly]
public NativeArray<int> input;

上の例では、読み取り専用アクセス権を持つジョブと、NativeContainer への読み取り専用アクセス権を持つその他のジョブを同時に実行することができます。

ノート: ジョブ内からの静的データへのアクセスに対する保護はありません。静的データにアクセスすると、すべての安全システムを回避し、Unity をクラッシュさせる可能性があります。詳細は、C# Job System のヒントとトラブルシューティング を参照してください。

NativeContainer アロケーター

NativeContainer を作成するときに、必要なメモリ割り当てのタイプを指定する必要があります。割り当てタイプは、ジョブが実行される時間の長さによって異なります。これにより、各状況で最高のパフォーマンスが得られるように割り当てを調整できます。

NativeContainer メモリ割り当てと解放には 3 つの Allocator タイプがあります。NativeContainer をインスタンス化するときに適切なものを指定する必要があります。

  • Allocator.Temp は最も高速な割り当てが可能です。寿命が 1 フレーム以下の割り当てに使ってください。ただし、Temp を使って NativeContainer の割り当てをジョブに渡すことはできません。
  • Allocator.TempJobTemp よりも遅い割り当てですが、Persistent よりも速い割り当てです。4 フレームの寿命内でスレッドに安全な割り当てを行うために使用します。重要: このタイプの割り当ては、4 フレーム以内に Dispose を行わなければなりません。ほとんどの小規模なジョブでは、この NativeContainer 割り当てタイプを使用します。
  • Allocator.Persistent は最も遅い割り当てですが、好きなだけ長く、必要な場合は、アプリケーションの生存期間を通して使用できます。これは、malloc (英語) への直接呼び出しのためのラッパーです。長いジョブはこの割り当てタイプを使用できます。パフォーマンスが重要な場合は Persistent を使用しないでください。

例:

NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);

ノート: 上の例の数字 1 は、 NativeArray のサイズを示しています。この場合、配列要素は 1 つだけです。理由は、result に 1 つのデータしか格納しないためです。

C# Job System の安全システム
ジョブの作成