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

580 views Asked by At

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.

1

There are 1 answers

2
Andrew Łukasik On BEST ANSWER

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