Rewindable allocator
Use a custom rewindable allocator with Unity Collections to pre-allocate memory and reclaim it in a single operation.
Read time 10 minutesLast updated 7 days ago
A rewindable allocator is a custom allocator that works in a similar way to a linear allocator. A rewindable allocator pre-allocates blocks of memory in advance. To rewind in this context means resetting the allocator back to its starting point so that all memory previously allocated can be used again.
A rewindable allocator is useful when you make many temporary allocations that all share the same lifetime. For example, all the scratch data created during a single frame, a single job batch, or one pass of an algorithm.
Instead of freeing each allocation one by one, you allocate throughout your work, and then rewind the allocator once at the end to reclaim everything in a single operation. Freeing allocations in a single operation means you don't have to track any allocation individually, which reduces the risk of memory leaks. This is a thread-safe operation that multiple threads can allocate from concurrently.
A typical pattern looks like this:
- Allocate a rewindable allocator once.
- During the frame or pass, make as many temporary allocations from it as you need.
- At the end of the frame or pass, rewind the allocator to reclaim all of them at once.
- Repeat for the next frame or pass, reusing the same memory blocks.
When you request memory from a rewindable allocator, it selects a range of memory from its pre-allocated block and assigns it to use. The minimum alignment of rewindable allocations is 64 bytes. After it uses all the existing blocks of memory, the rewindable allocator allocates another block of memory. It doubles the size of the new block until it reaches a maximum block size. When it reaches this point, the rewindable allocator adds the maximum block size to its previous block size to increase its block size linearly.
When you rewind an allocator, the allocator keeps the memory blocks that it used before to improve performance and disposes the rest of the blocks. When you request to free or dispose a memory allocation from a rewindable allocator, nothing happens unless you set to true. When this property is true, the rewindable allocator rewinds a memory block when it frees the last allocation from the block.
RewindableAllocation.EnableBlockFreeDeclare and create a rewindable allocator
To create a rewindable allocator, you must do the following:
- Allocate memory to hold the rewindable allocator.
- Add an entry in the global allocator table to register the allocator.
- Pre-allocate the allocator's first memory block to initialize it.
You can use the wrapper to create a rewindable allocator.
AllocatorHelperThe following example declares and creates a rewindable allocator:
// Example user structureinternal struct ExampleStruct{ // Use AllocatorHelper to help creating a rewindable alloctor AllocatorHelper<RewindableAllocator> rwdAllocatorHelper; // Rewindable allocator property for accessibility public ref RewindableAllocator RwdAllocator => ref rwdAllocatorHelper.Allocator; // Create the rewindable allocator void CreateRewindableAllocator(AllocatorManager.AllocatorHandle backgroundAllocator, int initialBlockSize, bool enableBlockFree = false) { // Allocate the rewindable allocator from backgroundAllocator and register the allocator rwdAllocatorHelper = new AllocatorHelper<RewindableAllocator>(backgroundAllocator); // Allocate the first memory block with initialBlockSize in bytes, and indicate whether // to enable the rewindable allocator with individual block free through enableBlockFree RwdAllocator.Initialize(initialBlockSize, enableBlockFree); }}
Use a rewindable allocator to allocate memory
For collection types, allocation from a rewindable allocator is similar to a built-in allocator, except you must use to create a from a rewindable allocator. When you use a rewindable allocator to create a native collection type, its safety handle is added to the list of child safety handles of the rewindable allocator.
Native-CollectionHelper.CreateNativeArrayNativeArrayFor collection types, you must use to allocate memory from a rewindable allocator.
Unsafe-AllocatorManager.AllocateYou don't need to dispose individual allocations. When all allocations aren't needed anymore, call the method of a rewindable allocator to free all its allocations. When you rewind the rewindable allocator, it invalidates and unregisters its child allocators, and invalidates all its child safety handles. For native collection types, the disposal safety checks throw an exception if the rewindable allocator has rewound.
RewindThis example method shows how to use a rewindable allocator to create and allocate native containers:
UseRewindableAllocator// Sample code to use rewindable allocator to allocate containers public unsafe void UseRewindableAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList, out byte* bytePtr) { // Use rewindable allocator to allocate a native array, no need to dispose the array manually // CollectionHelper is required to create/allocate native array from a custom allocator. nativeArray = CollectionHelper.CreateNativeArray<int, RewindableAllocator>(100, ref RwdAllocator); nativeArray[0] = 0xFE; // Use rewindable allocator to allocate a native list, do not need to dispose the list manually nativeList = new NativeList<int>(RwdAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } // Use custom allocator to allocate a byte buffer. bytePtr = (byte*)AllocatorManager.Allocate(ref RwdAllocator, sizeof(byte), sizeof(byte), 10); bytePtr[0] = 0xAB; } #endregion // allocator-rewindable-use #region allocator-rewindable-free // Free all allocations from the rewindable allocator public void FreeRewindableAllocator() { RwdAllocator.Rewind(); } #endregion // allocator-rewindable-free #region allocator-rewindable-dispose // Dispose the rewindable allocator void DisposeRewindableAllocator() { // Dispose all the memory blocks in the rewindable allocator RwdAllocator.Dispose(); // Unregister the rewindable allocator and dispose it rwdAllocatorHelper.Dispose(); } #endregion // allocator-rewindable-dispose}internal class ExampleStructSampleUsage{ // Initial block size of the rewindable allocator. const int IntialBlockSize = 128 * 1024; [Test] public unsafe void UseRewindableAllocator_Works() { ExampleStruct exampleStruct = new ExampleStruct(IntialBlockSize); // Allocate native array and native list from rewindable allocator exampleStruct.UseRewindableAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList, out byte* bytePtr); // Still able to access the native array, native list and byte buffer Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); Assert.AreEqual(bytePtr[0], 0xAB); // Free all memories allocated from the rewindable allocator // No need to dispose the native array and native list exampleStruct.FreeRewindableAllocator();#if ENABLE_UNITY_COLLECTIONS_CHECKS // Object disposed exception throws because nativeArray is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeList[10] = 0x10; });#endif // Dispose the user structure exampleStruct.Dispose(); }}#endregion // allocator-rewindable-example
Free all allocated memory of a rewindable allocator
When you call on the rewindable allocator, it performs the following operations:
RewindableAllocator.Rewind- Invalidates and unregisters all the allocator handle's child allocators.
- Invalidates all its child safety handles.
The example method shows how to free all allocations from the rewindable allocator by calling .
FreeRewindableAllocatorRewindableAllocator.Rewind// Free all allocations from the rewindable allocator public void FreeRewindableAllocator() { RwdAllocator.Rewind(); } #endregion // allocator-rewindable-free #region allocator-rewindable-dispose // Dispose the rewindable allocator void DisposeRewindableAllocator() { // Dispose all the memory blocks in the rewindable allocator RwdAllocator.Dispose(); // Unregister the rewindable allocator and dispose it rwdAllocatorHelper.Dispose(); } #endregion // allocator-rewindable-dispose}internal class ExampleStructSampleUsage{ // Initial block size of the rewindable allocator. const int IntialBlockSize = 128 * 1024; [Test] public unsafe void UseRewindableAllocator_Works() { ExampleStruct exampleStruct = new ExampleStruct(IntialBlockSize); // Allocate native array and native list from rewindable allocator exampleStruct.UseRewindableAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList, out byte* bytePtr); // Still able to access the native array, native list and byte buffer Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); Assert.AreEqual(bytePtr[0], 0xAB); // Free all memories allocated from the rewindable allocator // No need to dispose the native array and native list exampleStruct.FreeRewindableAllocator();#if ENABLE_UNITY_COLLECTIONS_CHECKS // Object disposed exception throws because nativeArray is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeList[10] = 0x10; });#endif // Dispose the user structure exampleStruct.Dispose(); }}#endregion // allocator-rewindable-example
Dispose a rewindable allocator
To dispose a rewindable allocator, you must do the following:
- Dispose all the memory blocks of the rewindable allocator from .
Allocator.Persistent - Unregister the allocator.
- Dispose the memory used to store the allocator.
The following example adds a method that disposes a rewindable allocator using :
DisposeRewindableAllocatorDispose// Dispose the rewindable allocator void DisposeRewindableAllocator() { // Dispose all the memory blocks in the rewindable allocator RwdAllocator.Dispose(); // Unregister the rewindable allocator and dispose it rwdAllocatorHelper.Dispose(); } #endregion // allocator-rewindable-dispose}internal class ExampleStructSampleUsage{ // Initial block size of the rewindable allocator. const int IntialBlockSize = 128 * 1024; [Test] public unsafe void UseRewindableAllocator_Works() { ExampleStruct exampleStruct = new ExampleStruct(IntialBlockSize); // Allocate native array and native list from rewindable allocator exampleStruct.UseRewindableAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList, out byte* bytePtr); // Still able to access the native array, native list and byte buffer Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); Assert.AreEqual(bytePtr[0], 0xAB); // Free all memories allocated from the rewindable allocator // No need to dispose the native array and native list exampleStruct.FreeRewindableAllocator();#if ENABLE_UNITY_COLLECTIONS_CHECKS // Object disposed exception throws because nativeArray is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeList[10] = 0x10; });#endif // Dispose the user structure exampleStruct.Dispose(); }}#endregion // allocator-rewindable-example
Full example of a rewindable allocator
The following is a full example of how to use a rewindable allocator:
using System;using NUnit.Framework;using Unity.Collections;// Example user structureinternal struct ExampleStruct{ // Use AllocatorHelper to help creating a rewindable alloctor AllocatorHelper<RewindableAllocator> rwdAllocatorHelper; // Rewindable allocator property for accessibility public ref RewindableAllocator RwdAllocator => ref rwdAllocatorHelper.Allocator; // Create the rewindable allocator void CreateRewindableAllocator(AllocatorManager.AllocatorHandle backgroundAllocator, int initialBlockSize, bool enableBlockFree = false) { // Allocate the rewindable allocator from backgroundAllocator and register the allocator rwdAllocatorHelper = new AllocatorHelper<RewindableAllocator>(backgroundAllocator); // Allocate the first memory block with initialBlockSize in bytes, and indicate whether // to enable the rewindable allocator with individual block free through enableBlockFree RwdAllocator.Initialize(initialBlockSize, enableBlockFree); } // Constructor of user structure public ExampleStruct(int initialBlockSize) { this = default; CreateRewindableAllocator(Allocator.Persistent, initialBlockSize, false); } // Dispose the user structure public void Dispose() { DisposeRewindableAllocator(); } #region allocator-rewindable-use // Sample code to use rewindable allocator to allocate containers public unsafe void UseRewindableAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList, out byte* bytePtr) { // Use rewindable allocator to allocate a native array, no need to dispose the array manually // CollectionHelper is required to create/allocate native array from a custom allocator. nativeArray = CollectionHelper.CreateNativeArray<int, RewindableAllocator>(100, ref RwdAllocator); nativeArray[0] = 0xFE; // Use rewindable allocator to allocate a native list, do not need to dispose the list manually nativeList = new NativeList<int>(RwdAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } // Use custom allocator to allocate a byte buffer. bytePtr = (byte*)AllocatorManager.Allocate(ref RwdAllocator, sizeof(byte), sizeof(byte), 10); bytePtr[0] = 0xAB; } #endregion // allocator-rewindable-use #region allocator-rewindable-free // Free all allocations from the rewindable allocator public void FreeRewindableAllocator() { RwdAllocator.Rewind(); } #endregion // allocator-rewindable-free #region allocator-rewindable-dispose // Dispose the rewindable allocator void DisposeRewindableAllocator() { // Dispose all the memory blocks in the rewindable allocator RwdAllocator.Dispose(); // Unregister the rewindable allocator and dispose it rwdAllocatorHelper.Dispose(); } #endregion // allocator-rewindable-dispose}internal class ExampleStructSampleUsage{ // Initial block size of the rewindable allocator. const int IntialBlockSize = 128 * 1024; [Test] public unsafe void UseRewindableAllocator_Works() { ExampleStruct exampleStruct = new ExampleStruct(IntialBlockSize); // Allocate native array and native list from rewindable allocator exampleStruct.UseRewindableAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList, out byte* bytePtr); // Still able to access the native array, native list and byte buffer Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); Assert.AreEqual(bytePtr[0], 0xAB); // Free all memories allocated from the rewindable allocator // No need to dispose the native array and native list exampleStruct.FreeRewindableAllocator();#if ENABLE_UNITY_COLLECTIONS_CHECKS // Object disposed exception throws because nativeArray is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeList[10] = 0x10; });#endif // Dispose the user structure exampleStruct.Dispose(); }}#endregion // allocator-rewindable-example