Version: 2023.1
言語: 日本語
ジョブの概要
カスタムネイティブコンテナの実装

スレッドセーフタイプ

ジョブシステムは、Burst コンパイラー と併用するのが最も効果的です。Burst はマネージオブジェクトをサポートしていないので、ジョブのデータにアクセスするにはアンマネージタイプを使う必要があるためです。これには Blittable 型 を使うか、Unity のビルトインの NativeContainer オブジェクトを使用できます。NativeContainer オブジェクトは、ネイティブメモリのためのスレッドセーフな C# ラッパーです。NativeContainer オブジェクトはジョブがコピーで作業するのではなく、メインスレッド と共有されるデータにアクセスすることを可能にします。

NativeContainer の型

Unity.Collections 名前空間には、以下のビルトイン NativeContainer オブジェクトがあります。

  • NativeArray: マネージドコードにネイティブメモリのバッファを公開するアンマネージ配列
  • NativeSlice: NativeArray の特定の位置から特定の長さのサブセットを取得します。

ノート: Collections パッケージ には、追加の NativeContainers が含まれています。追加の型の完全なリストについては、Collections ドキュメントの Collection 型 を参照してください。

読み書きアクセス

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

しかし、ジョブがNativeContainer インスタンスに書き込む必要がない場合は、以下のように NativeContainer[ReadOnly] 属性を設定することができます。

[ReadOnly]
public NativeArray<int> input;

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

メモリ割り当て

NativeContainer インスタンスを作成するときに、必要なメモリ割り当てタイプを指定する必要があります。使用する割り当てタイプは、ネイティブコンテナを使用可能な状態にしておく時間によって決まります。こうすることで、各状況で可能な限り最高のパフォーマンスが得られるように割り当てを調整できます。

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

  • Allocator.Temp: 最も高速な割り当てが可能です。スパンが 1 フレーム以下の割り当てに使用します。ただし、Temp を使って、ジョブのメンバーフィールドに保管されている NativeContainer インスタンスに割り当てを渡すことはできません。
  • Allocator.TempJob: Temp よりもスピードの遅い割り当てですが、Persistent よりも高速です。これは、4 フレーム以内のスパンの割り当ての、スレッドセーフな割り当てに使用します。4 フレーム以内でこの割り当ての Dispose を行わないと、コンソールはネイティブコードから生成された警告を出力します。ほとんどの小さなジョブはこの割り当てタイプを使用します。
  • Allocator.Persistent: 最もスピードの遅い割り当てですが、好きなだけ長く 、必要な場合は、アプリケーションの生存期間を通して使用できます。これは、malloc への直接呼び出しのためのラッパーです。長いジョブはこの割り当てタイプを使用できます。パフォーマンスが重要な場合は Persistent を使用しないでください。

例:

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

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

NativeContainer と安全システム

安全システムは すべての NativeContainer インスタンスに組み込まれています。すべての NativeContainer インスタンスへの読み込みや書き込みを追跡し、その情報を使って NativeContainer の使用に一定のルールを適用し、複数のジョブやスレッドにまたがって決定論的な動作を行うようにします。

例えば、2 つの個々にスケジュールされたジョブが同じ NativeArray に書き込む場合、どちらのジョブが先に実行されるか予測できないため、これは安全ではありません。つまり、どちらのジョブがもう一方のジョブのデータを上書きするかわからないということです。安全システムは、2 番目のジョブがスケジュールされる際に、理由と問題を解決する方法を説明する明確なエラーメッセージとともに例外をスローします。

同じ NativeContainer インスタンスに書き込む 2 つのジョブをスケジュールする場合、依存関係を持つジョブ をスケジュールできます。最初のジョブが NativeContainer に書き込み、実行が終了すると、次のジョブが同じ NativeContainer を安全に読み書きします。依存関係を導入することで、ジョブが常に一貫した順序で実行され、NativeContainer 内の結果のデータが決定論的であることが保証されます。

安全システムは、複数のジョブが同じデータを並行して読み取ることを可能にします。

これらの読み取りと書き込みの制限は、メインスレッドからデータにアクセスするときにも適用されます。たとえば、NativeContainer に書き込むジョブが完了する前に NativeContainer の内容を読み取ろうとすると、安全システムはエラーをスローします。同様に、NativeContainer への読み取りまたは書き込みを保留しているジョブがまだあるときに NativeContainer に書き込もうとすると、安全システムはエラーをスローします。

また、NativeContainers は ref return を実装していないため、NativeContainer の内容を直接変更することはできません。例えば、nativeArray[0]++;var temp = nativeArray[0]; temp++; と書くのと同じで、nativeArray の値は更新されません。

代わりに、インデックスのデータからローカルの一時的なコピーを作成し、そのコピーを変更して、それを元のデータに保存する必要があります。以下のように行ないます。

MyStruct temp = myNativeArray[i];
temp.memberVariable = 0;
myNativeArray[i] = temp;

その他の参考資料

ジョブの概要
カスタムネイティブコンテナの実装