Create and use a custom allocator
Create and use a custom allocator for unmanaged collections.
Read time 12 minutesLast updated 7 days ago
Once you've defined a custom allocator, you can add it to your structure or class.
Declare and create a custom allocator
The first step is to declare and create the custom allocator. You must do the following:
- Allocate memory to hold the custom allocator.
- Register the allocator by adding an entry in a global allocator table.
- Initialize the allocator if necessary.
The wrapper helps in creating a custom allocator. The following examples show how to declare and create a custom allocator defined in the Example custom allocator.
AllocatorHelper// Example user structure that contains the custom allocatorinternal struct ExampleCustomAllocatorStruct{ // Use AllocatorHelper to help creating the example custom alloctor AllocatorHelper<ExampleCustomAllocator> customAllocatorHelper; // Custom allocator property for accessibility public ref ExampleCustomAllocator customAllocator => ref customAllocatorHelper.Allocator; // Create the example custom allocator void CreateCustomAllocator(AllocatorManager.AllocatorHandle backgroundAllocator, byte initialValue) { // Allocate the custom allocator from backgroundAllocator and register the allocator customAllocatorHelper = new AllocatorHelper<ExampleCustomAllocator>(backgroundAllocator); // Set the initial value to initialize the memory customAllocator.Initialize(initialValue); }}
Use a custom allocator to allocate memory
For collection types, allocation from a custom allocator is similar to built-in allocators. The notable exception is when creating or deallocating a , for which you must use and respectively.
Native-NativeArrayCollectionHelper.CreateNativeArrayCollectionHelper.DisposeWhen you use a custom allocator to create a collection type, its safety handle is added to the list of child safety handles of the custom allocator. When you rewind the allocator handle of a custom allocator, it invalidates and unregisters all its child allocators, and invalidates all its child safety handles. For collection types, the disposal safety checks throw an exception if the allocator handle has rewound.
Native-Native-For collection types such as , , and , pass the custom allocator's to the collection's constructor. The collection retains this handle and uses it when you call the collection's own method, so you don't need to allocate or free its memory manually.
Unsafe-UnsafeList<T>UnsafeHashMap<TKey, TValue>UnsafeStreamAllocatorManager.AllocateDisposeTo allocate raw memory from a custom allocator, use to allocate the memory and to deallocate it. The following example demonstrates this raw byte-buffer case:
AllocatorManager.AllocateAllocatorManager.Free// Sample code to use the custom allocator to allocate containers public void UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref customAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); // Free the byte buffer. AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } } #endregion // allocator-custom-use public int AllocationCount => customAllocator.AllocationCount; public void UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int>(100, customAllocator.ToAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } }}internal class ExampleCustomAllocatorStructUsage{ const int IntialValue = 0xAB; // Test code. [Test] public void UseCustomAllocator_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator exampleStruct.UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray) ; // Dispose the native list nativeList.Dispose();#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 // Check allocation count after dispose the native array and native list Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void UseCustomAllocatorHandle_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator handle exampleStruct.UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray); nativeList.Dispose();#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 Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void CustomAllocatorHandle_MultiThreadWorks() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); var taskList = new List<Task>(); // create 128 native array with another threads for (var i = 0; i < 128; i++) { var task = Task.Run(() => { var nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref exampleStruct.customAllocator, NativeArrayOptions.UninitializedMemory); CollectionHelper.Dispose(nativeArray); }); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); exampleStruct.Dispose(); }}#endregion // allocator-custom-user-struct
Dispose a custom allocator
To dispose a custom allocator, the following must happen:
- The custom allocator must rewind its allocator handle which invalidates and unregisters all the allocator handle's child allocators, and invalidates all its child safety handles.
- You must unregister the allocator.
- You must dispose the memory used to store the allocator.
The example method in the user structure shows how to dispose a custom allocator:
DisposeCustomAllocator// Dispose the custom allocator void DisposeCustomAllocator() { // Dispose the custom allocator customAllocator.Dispose(); // Unregister the custom allocator and dispose it customAllocatorHelper.Dispose(); } #endregion // allocator-custom-dispose public ExampleCustomAllocatorStruct(byte initialValue) { this = default; CreateCustomAllocator(Allocator.Persistent, initialValue); } public void Dispose() { DisposeCustomAllocator(); } #region allocator-custom-use // Sample code to use the custom allocator to allocate containers public void UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref customAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); // Free the byte buffer. AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } } #endregion // allocator-custom-use public int AllocationCount => customAllocator.AllocationCount; public void UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int>(100, customAllocator.ToAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } }}internal class ExampleCustomAllocatorStructUsage{ const int IntialValue = 0xAB; // Test code. [Test] public void UseCustomAllocator_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator exampleStruct.UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray) ; // Dispose the native list nativeList.Dispose();#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 // Check allocation count after dispose the native array and native list Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void UseCustomAllocatorHandle_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator handle exampleStruct.UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray); nativeList.Dispose();#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 Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void CustomAllocatorHandle_MultiThreadWorks() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); var taskList = new List<Task>(); // create 128 native array with another threads for (var i = 0; i < 128; i++) { var task = Task.Run(() => { var nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref exampleStruct.customAllocator, NativeArrayOptions.UninitializedMemory); CollectionHelper.Dispose(nativeArray); }); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); exampleStruct.Dispose(); }}#endregion // allocator-custom-user-struct
Full example of a custom allocator
The following is a full example of how to use a custom allocator:
// Example user structure that contains the custom allocatorinternal struct ExampleCustomAllocatorStruct{ AllocatorHelper<ExampleCustomAllocator> customAllocatorHelper; public ref ExampleCustomAllocator customAllocator => ref customAllocatorHelper.Allocator; // Create the example custom allocator void CreateCustomAllocator(AllocatorManager.AllocatorHandle backgroundAllocator, byte initialValue) { // Allocate the custom allocator from backgroundAllocator and register the allocator customAllocatorHelper = new AllocatorHelper<ExampleCustomAllocator>(backgroundAllocator); // Set the initial value to initialize the memory customAllocator.Initialize(initialValue); } #region allocator-custom-dispose // Dispose the custom allocator void DisposeCustomAllocator() { // Dispose the custom allocator customAllocator.Dispose(); // Unregister the custom allocator and dispose it customAllocatorHelper.Dispose(); } #endregion // allocator-custom-dispose public ExampleCustomAllocatorStruct(byte initialValue) { this = default; CreateCustomAllocator(Allocator.Persistent, initialValue); } public void Dispose() { DisposeCustomAllocator(); } #region allocator-custom-use // Sample code to use the custom allocator to allocate containers public void UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref customAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); // Free the byte buffer. AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } } #endregion // allocator-custom-use public int AllocationCount => customAllocator.AllocationCount; public void UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int>(100, customAllocator.ToAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } }}internal class ExampleCustomAllocatorStructUsage{ const int IntialValue = 0xAB; // Test code. [Test] public void UseCustomAllocator_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator exampleStruct.UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray) ; // Dispose the native list nativeList.Dispose();#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 // Check allocation count after dispose the native array and native list Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void UseCustomAllocatorHandle_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator handle exampleStruct.UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray); nativeList.Dispose();#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 Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void CustomAllocatorHandle_MultiThreadWorks() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); var taskList = new List<Task>(); // create 128 native array with another threads for (var i = 0; i < 128; i++) { var task = Task.Run(() => { var nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref exampleStruct.customAllocator, NativeArrayOptions.UninitializedMemory); CollectionHelper.Dispose(nativeArray); }); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); exampleStruct.Dispose(); }}#endregion // allocator-custom-user-struct