Search code examples
c#unity-game-enginejob-schedulingunity-burst

What's the difference between the Allocators of a NativeArray?


When should I use which value (None, Invalid, Temp, TempJob, Persistent, FirstUserIndex, AudioKernal)

How does each Allocator affect allocation and the lifespan of the NativeArray in implementation?

I can't figure out how the lifespans/implementations differ for each Allocator value when instantiating a NativeArray. I've checked the docs and IntelliSense to no avail, besides being shown which Enum values are available.


Solution

  • In-depth look at allocators:

    https://www.jacksondunstan.com/articles/5406

    Allocator.Temp

    Offers fastest allocation time. Is valid for a single frame. No need to call Dispose(). Can't be assigned to field of a job struct (because job's lifetime can be over 1 frame) but can be allocated by a job execution code.

    struct AJob : IJob
    {
        void IJob.Execute ()
        {
            var tempArray = new NativeArray<int>( 128 , Allocator.Temp );
            for( int i=0 ; i<tempArray.Length ; i++ )
            {
                /* does something useful with it */
            }
            // tempArray.Dispose() call is optional as Temp will deallocate automagically
        }
    }
    

    Allocator.TempJob

    Fast, temporary allocations for job struct fields.

    public class LetsScheduleAJob : MonoBehaviour
    {
        [SerializeField] int[] _inputs = new int[]{ 1 , 2 , 3 , 4 , 5 , 6 , 7 };
        public JobHandle Dependency;
        void Update ()
        {
            Dependency.Complete();// makes sure previously-scheduled job is completed by now
    
            var temporaryData = new NativeArray<int>( _inputs , Allocator.TempJob );
            Dependency = new SumUpJob{
                NumbersToSumUp = temporaryData ,
            }.Schedule();
            temporaryData.Dispose( Dependency );// deallocates on job completion
        }
    }
    
    struct SumUpJob : IJob
    {
        public NativeArray<int> NumbersToSumUp;
        void IJob.Execute ()
        {
            int sum = 0;
            for( int i=0 ; i<NumbersToSumUp.Length ; i++ )
                sum += NumbersToSumUp[i];
            Debug.Log($"sum: {sum}");
        }
    }
    

    Allocator.Persistent

    Slow allocation. No lifetime restrictions. Must call Dispose() to free (memory leak otherwise). Ideal for data that needs to always exist for a system to function.

    public class LetsScheduleAJob : MonoBehaviour
    {
        [SerializeField] int[] _inputs = new int[]{ 1 , 2 , 3 , 4 , 5 , 6 , 7 };
        NativeArray<int> _data;
        public JobHandle Dependency;
        void Awake ()
        {
            _data = new NativeArray<int>( _inputs.Length , Allocator.Persistent );
            _data.CopyFrom( _inputs );
        }
        void OnDestroy ()
        {
            Dependency.Complete();
            if( _data.IsCreated ) _data.Dispose();// I (class) allocated this array so (only) I Dispose it
        }
        void Update ()
        {
            Dependency.Complete();// makes sure previously-scheduled job is completed by now
    
            _data.CopyFrom( _inputs );
    
            Dependency = new SumUpJob{
                NumbersToSumUp = _data ,
            }.Schedule();
        }
    }
    

    Allocator.None

    Allocator.None is for these rare cases where you need to say "no allocation happened here" to create a valid NativeArray of which lifetime is decided elsewhere. For example, to access managed memory as if it is just another NativeArray:

    void* ptr = UnsafeUtility.PinGCArrayAndGetDataAddress(managedArray, out gcHandle);
    var nativeArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(ptr, managedArray.Length, Allocator.None);
    

    source: https://gist.github.com/andrew-raphael-lukasik/812e152f95e38cc13c44e5040c5739bc

    Invalid

    Signals allocation failure. Don't use.

    FirstUserIndex

    No idea. Don't use.

    @derHugo wrote: FirstUserIndex is just another check index, you can define custom allocators (see AllocatorManager.Register), you wouldn't pass this one in yourself though, this is internally handled

    AudioKernel

    No idea. Don't use.

    @derHugo wrote: Seems to be very use case specific for jobs related to the DOTS DSPGraph