Why is NativeArray needed to obtain return values from Unity's Job System?
Asked Answered
B

3

5

Using the code block below as reference, a single update provides the following results:

intReturn = 0

inputArrayReturn = 1

internalArrayReturn = 1

The only value that makes sense to me here is internalArrayReturn.

Why doesn't intReturn equal 1? What is going on to cause the stuct's value to not change even though during execution it should have had 1 added to it? If it is simply a fundamental difference between NativeArray and int, could someone please explain or point me to a reference that will help me understand this better?

Also, why doesn't inputArrayReturn equal 0? NativeArray is a struct, so shouldn't it be treated as a copy of values rather than a reference when the line testArray = testArrayResults executes? If this was the case, then inputArrayReturn would stay equal to 0 rather than being updated to the same value contained in testJob.testArray.

using UnityEngine;
using Unity.Jobs;
using Unity.Collections;

public class ParallelTesting : MonoBehaviour
{
    void Update()
    {
        NativeArray<int> testArrayResults = new NativeArray<int>(1, Allocator.TempJob);

        TestJob testJob = new TestJob
        {
            testInt = 0,
            testArray = testArrayResults
        };

        JobHandle testJobHandle = testJob.Schedule();
        testJobHandle.Complete();

        int intReturn = testJob.testInt;
        int inputArrayReturn = testArrayResults[0];
        int internalArrayReturn = testJob.testArray[0];

        testArrayResults.Dispose();
    }

    public struct TestJob : IJob
    {
        public int testInt;
        public NativeArray<int> testArray;

        public void Execute()
        {
            testInt += 1;
            testArray[0] = testInt;
        }
    }
}
Baun answered 12/12, 2020 at 0:36 Comment(0)
O
4

No expert but I'ld say NativeArray is specifically designed to be thread save and basically a shared memory between the Unity main thread and the job system/runner.

And yes NativeArray is a struct but in fact if you look at the source code it basically only is a wrapper holding a pointer to the actual native buffer in the memory.

    public unsafe struct NativeArray<T> : IDisposable, IEnumerable<T>, IEquatable<NativeArray<T>> where T : struct
    {
        [NativeDisableUnsafePtrRestriction]
        internal void*                    m_Buffer;
        internal int                      m_Length;

        internal int                      m_MinIndex;
        internal int                      m_MaxIndex;
        
        ...

That's why both

int inputArrayReturn = testArrayResults[0];
int internalArrayReturn = testJob.testArray[0];
  

access the same external memory via the same pointer and therefore return both the same and correct value.

The "normal" int however is not necessarily thread safe since it is in no external memory allocated (see Heap and Stack) and your Unity main thread doesn't know that the job internally changed it's value while it was on a different thread/core.

Oxidase answered 12/12, 2020 at 15:24 Comment(1)
> The "normal" int however is not necessarily thread safe since it is in no external memory allocated being threadsafe is little to do with how the memory is allocated, or it being external, in this situation someone had to do things to make it behave this way. if an int, or any 8-byte value or smaller, is written once without special care, and nothing fancy is being done, its already as threadsafe as it can be. even in the case withmultiple writes the fix would be to use interlocked compare/exchange and nothing to do with memory allocation... and the compiler has enough info to do this.Sidwell
C
2

Why doesn't intReturn equal 1?

Because Unity.Jobs work with dedicated memory blocks allocated outside managed memory (native containers, Unity.Collections, pointers) and testInt, while marshaled for worker threads, is not allocated anywhere outside stack.


Also, why doesn't inputArrayReturn equal 0?

Because both testArrayResults and testJob.testArray are copies of the same pointer so both reference the same allocation that happened at new NativeArray<int>(1, Allocator.TempJob).


Why is NativeArray needed to obtain return values from Unity's Job System?

It's just a good standardized solution to thread safety problem as tooling around those containers warn you about all manners of dependencies, race conditions etc.

NOTE: You can use pointers with [NativeDisableUnsafePtrRestriction] attribute. But it's safer to just stick with native containers.

Christa answered 12/12, 2020 at 15:37 Comment(0)
S
0

the real reasoning imo is poor craftsmanship and documentation. i hope it improves. i just spent a good while debugging the same stuff...

having worked with multithreaded native code forever, there is no genuine reason for this beyond shoddy implementation or corner cutting. however, as the detailed answer accepted already suggests, there are implementation details bleeding through here.

incidentally a normal int is pretty damned threadsafe its the operations on it that matter, and 'interlocked compare and exchange' can handle all of that trivially... even in the absense of other atomic operations

Sidwell answered 20/6, 2022 at 1:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.