ジョブシステムは、Burst コンパイラー と併用するのが最も効果的です。Burst はマネージオブジェクトをサポートしていないので、ジョブのデータにアクセスするにはアンマネージタイプを使う必要があるためです。これには Blittable 型 を使うか、Unity のビルトインの NativeContainer
オブジェクトを使用できます。NativeContainer
オブジェクトは、ネイティブメモリのためのスレッドセーフな C# ラッパーです。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
の使用に一定のルールを適用し、複数のジョブやスレッドにまたがって決定論的な動作を行うようにします。
例えば、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;