Library -Artifacts
6.2 KiB
uid |
---|
collections-allocation |
Using unmanaged memory
The Native-
and Unsafe-
collections in this package are allocated from unmanaged memory, meaning their existence is unknown to the garbage collector.
You are responsible for deallocating any unmanaged memory that you no longer need. Failing to deallocate large or numerous allocations can lead to wasting more and more memory, which may eventually slow down or even crash your program.
Allocators
An allocator governs some unmanaged memory from which you can make allocations. Different allocators organize and track their memory in different ways. The three standard provided allocators are:
Allocator.Temp
The fastest allocator. For very short-lived allocations. Temp allocations cannot be passed into jobs.
Each frame, the main thread creates a Temp allocator which is deallocated in its entirety at the end of the frame. Each job also creates one Temp allocator per thread, and these are deallocated in their entireties at the end of the job. Because a Temp allocator gets discarded as a whole, you actually don't need to manually deallocate your Temp allocations (in fact, doing so is a no-op).
Temp allocations are only safe to use within the thread where they were allocated. So while Temp allocations can be made within a job, main thread Temp allocations cannot be passed into a job. For example, a NativeArray that's Temp allocated in the main thread cannot be passed into a job.
Allocator.TempJob
The next fastest allocator. For short-lived allocations. TempJob allocations can be passed into jobs.
You are expected to deallocate your TempJob allocations within 4 frames of their creation. The number 4 was chosen because it's common to want allocations that last a couple frames: the limit of 4 accommodates this need with a comfortable extra margin.
For the Native-
collection types, the disposal safety checks will throw an exception if a TempJob allocation lives longer than 4 frames. For the Unsafe-
collection types, you are still expected to deallocate them within 4 frames, but no safety checks are performed to ensure you do so.
Allocator.Persistent
The slowest allocator. For indefinite lifetime allocations. Persistent allocations can be passed into jobs.
Because Persistent allocations are allowed to live indefinitely, no safety check can detect if a Persistent allocation has outlived its intended lifetime. Consequently, you should be extra careful to deallocate a Persistent allocation when you no longer need it.
Disposal (deallocation)
Each collection retains a reference to the allocator from which its memory was allocated because deallocation requires specifying the allocator.
- An
Unsafe-
collection'sDispose
method deallocates its memory. - A
Native-
collection'sDispose
method deallocates its memory and frees the handles needed for safety checks. - An enumerator's
Dispose
method is a no-op. The method is included only to fulfill theIEnumerator<T>
interface.
We often want to dispose a collection after the jobs which need it have run. The Dispose(JobHandle)
method creates and schedules a job which will dispose the collection, and this new job takes the input handle as its dependency. Effectively, the method differs disposal until after the dependency runs:
[!code-csallocation_dispose_job]
The IsCreated
property
The IsCreated
property of a collection is false only in two cases:
- Immediately after creating a collection with its default constructor.
- After
Dispose
has been called on the collection.
Understand, however, that you're not intended to use a collections's default constructor. It's only made available because C# requires all structs to have a public default constructor.
Also note that calling Dispose
on a collection sets IsCreated
to false only in that struct, not in any copies of the struct. Consequently, IsCreated
may still be true even after the collection's underlying memory was deallocated if...
Dispose
was called on a different copy of the struct.- Or the underlying memory was deallocated via an alias.
Aliasing
An alias is a collection which does not have its own allocation but instead shares the allocation of another collection, in whole or in part. For example, an UnsafeList can be created that doesn't allocate its own memory but instead uses a NativeList's allocation. Writing to this shared memory via the UnsafeList affects the content of the NativeList, and vice versa.
You do not need to dispose aliases, and in fact, calling Dispose
on an alias does nothing. Once an original is disposed, the aliases of that original can no longer be used:
[!code-csallocation_aliasing]
Aliasing can be useful in a few scenarios:
- Getting a collection's data in the form of another collection type without copying the data. For example, you can create an UnsafeList that aliases a NativeArray.
- Getting a subrange of a collection's data without copying the data. For example, you can create an UnsafeList that aliases a subrange of another list or array.
- Array reinterpretation.
Perhaps surprisingly, it's allowed for an Unsafe-
collection to alias a Native-
collection even though such cases undermine the safety checks. For example, if an UnsafeList aliases a NativeList, it's not safe to schedule a job that accesses one while also another job is scheduled that accesses the other, but the safety checks do not catch these cases. It is your responsibility to avoid such mistakes.
Array reinterpretation
A reinterpretation of an array is an alias of the array that reads and writes the content as a different element type. For example, a NativeArray<int>
which reinterprets a NativeArray<ushort>
shares the same bytes, but it reads and writes the bytes as ints instead of ushorts; because each int is 4 bytes while each ushort is 2 bytes, each int corresponds to two ushorts, and the reinterpretation has half the length of the original.
[!code-csallocation_reinterpretation]