アンマネージメモリの使用
このパッケージ内の Native-
および Unsafe-
コレクションは、アンマネージメモリから割り当てられるので、その存在がガベージコレクターに認識されません。
必要なくなったアンマネージメモリの割り当ての解除は、自身で行う必要があります。大規模または大量の割り当てを解除しないと、メモリの無駄な消費がますます増大し、プログラムの低速化やクラッシュを招く可能性があります。
アロケーター
アロケーターは、割り当てることができる一部のアンマネージメモリを管理します。メモリを構成および追跡する方法は、アロケーターによって異なります。標準で提供されるアロケーターは、以下の 3 つです。
Allocator.Temp
最速のアロケーター。きわめて短期間の割り当てに使用します。Temp 割り当てをジョブに渡すことはできません。
フレームごとにメインスレッドによって Temp アロケーターが作成され、フレームが終了するとその全体が割り当て解除されます。各ジョブでもスレッドごとに 1 つの Temp アロケーターが作成され、ジョブが終了するとその全体が割り当て解除されます。Temp アロケーターはまとめて破棄されるので、実際には Temp 割り当てを手動で解除する必要はありません (実際、手動で割り当て解除しても何の処理も行われません)。
Temp 割り当ては、割り当てられたスレッド内で使用する場合に限り安全です。つまり Temp 割り当てをジョブの内部で行うことはできますが、メインスレッドの Temp 割り当てをジョブに渡すことはできません。例えば、メインスレッドで Temp 割り当てされた NativeArray をジョブに渡すことはできません。
Allocator.TempJob
2 番目に高速なアロケーター。短期間の割り当てに使用します。TempJob 割り当てはジョブに渡すことができます。
作成から 4 フレーム以内に TempJob 割り当てを解除する必要があります。4 という数が指定されている理由は、2 フレーム持続する割り当てが必要になる場合が多く、4 フレームという制限ならそのニーズに余裕を持って対応できるからです。
Native-
コレクション型の場合、TempJob 割り当てが 4 フレーム後も持続していると、破棄安全性チェックで例外が発生します。Unsafe-
コレクション型の場合も、4 フレーム以内に割り当てを解除することが求められますが、それを確実にするための安全性チェックは実行されません。
Allocator.Persistent
最も遅いアロケーター。生存期間が無期限の割り当てに使用します。Persistent 割り当てはジョブに渡すことができます。
Persistent 割り当ては無期限に持続することが許可されているので、Persistent 割り当てが想定生存期間を過ぎても安全性チェックでは検出できません。そのため、Persistent 割り当てが必要なくなったら割り当てを解除するよう特に注意する必要があります。
破棄 (割り当ての解除)
割り当ての解除にはアロケーターの指定が必要なので、各コレクションには、メモリの割り当て元となるアロケーターへの参照が保持されます。
Unsafe-
コレクションのDispose
メソッドは、そのメモリの割り当てを解除します。Native-
コレクションのDispose
メソッドは、そのメモリの割り当てを解除し、安全性チェックに必要なハンドルを解放します。- Enumerator の
Dispose
メソッドは、何の処理も行いません。このメソッドは、IEnumerator<T>
インターフェースの実現のみを目的にして含まれています。
コレクションを必要とするジョブを実行した後に、そのコレクションを破棄したい場合がよくあります。Dispose(JobHandle)
メソッドは、コレクションを破棄するジョブを作成し、そのスケジュールを設定します。この新しいジョブには、依存関係として入力ハンドルが必要です。実質的には、このメソッドは依存関係が実行されるまで破棄を行いません。
NativeArray<int> nums = new NativeArray<int>(10, Allocator.TempJob);
// Create and schedule a job that uses the array.
ExampleJob job = new ExampleJob { Nums = nums };
JobHandle handle = job.Schedule();
// Create and schedule a job that will dispose the array after the ExampleJob has run.
// Returns the handle of the new job.
handle = nums.Dispose(handle);
IsCreated
プロパティ
コレクションの IsCreated
プロパティは、以下の 2 つのケースでのみ false になります。
- デフォルトのコンストラクターでコレクションを作成した直後。
- コレクションで
Dispose
が呼び出された後。
ただし、コレクションのデフォルトコンストラクターの使用は想定されていないという点を理解しておいてください。これが使用可能になっているのは、単に C# ではすべての構造体に public デフォルトコンストラクターが必要だからです。
同様に注意すべきなのは、コレクションで Dispose
を呼び出すと、その構造体のコピーではなく、構造体の内部でだけ IsCreated
が false に設定されるという点です。そのため、コレクションの基になるメモリの割り当てが解除された後でも、以下の場合は IsCreated
が true のままになる可能性があります。
Dispose
が構造体の別のコピーで呼び出された。- または、基になるメモリが エイリアス を介して割り当て解除された。
エイリアシング
エイリアスは、独自の割り当てを持たない代わりに、別のコレクションの割り当ての全体または一部を共有するコレクションです。例えば、独自のメモリを割り当てない代わりに NativeList の割り当てを使用する、UnsafeList を作成できます。この共有メモリへの UnsafeList を介した書き込みは NativeList の内容に影響します。また、その逆も然りです。
エイリアスを破棄する必要はなく、実際のところエイリアスで Dispose
を呼び出しても何も起きません。元が破棄されると、そのエイリアスは使用できなくなります。
NativeList<int> nums = new NativeList<int>(10, Allocator.TempJob);
nums.Length = 5;
// Create an array of 5 ints that aliases the content of the list.
NativeArray<int> aliasedNums = nums.AsArray();
// Modify the first element of both the array and the list.
aliasedNums[0] = 99;
// Only the original need be disposed.
nums.Dispose();
// Throws an ObjectDisposedException because disposing
// the original deallocates the aliased memory.
aliasedNums[0] = 99;
エイリアシングは、以下のいくつかのシナリオで役に立ちます。
- コレクションのデータをコピーすることなく別のコレクション型で取得する。例えば、UnsafeList を NativeArray のエイリアスとして作成できます。
- コレクションのデータをコピーすることなく、その部分範囲を取得する。例えば、別のリストや配列の部分範囲のエイリアスとして UnsafeList を作成できます。
- 配列の再解釈。
驚くかもしれませんが、Unsafe-
コレクションを Native-
コレクションのエイリアスにできます。ただし安全性チェックの効果は薄れます。例えば、UnsafeList を NativeList のエイリアスにした場合、一方にアクセスするジョブをスケジュールしつつ、もう一方にアクセスする別のジョブもスケジュールするのは安全ではありませんが、安全性チェックでこれらのケースは検出されません。そのようなミスは、自身の責任で回避する必要があります。
配列の再解釈
配列の再解釈とは、異なる要素型として内容の読み取りや書き込みを行う配列のエイリアスです。例えば、NativeArray<ushort>
を再解釈した NativeArray<int>
は、同じバイトを共有しますが、ushort ではなく int としてバイトの読み取りや書き込みを行います。int は 4 バイト単位なのに対して ushort は 2 バイト単位なので、各 int が 2 つの ushort に対応し、再解釈の長さは元の半分になります。
NativeArray<int> ints = new NativeArray<int>(10, Allocator.Temp);
// Length of the reinterpreted array is 20
// (because it has two shorts per one int of the original).
NativeArray<short> shorts = ints.Reinterpret<int, short>();
// Modifies the first 4 bytes of the array.
shorts[0] = 1;
shorts[1] = 1;
int val = ints[0]; // val is 65537 (2^16 + 2^0)
// Like with other aliased collections, only the original
// needs to be disposed.
ints.Dispose();
// Throws an ObjectDisposedException because disposing
// the original deallocates the aliased memory.
shorts[0] = 1;