Is it okay to read a NativeCounter.Concurrent's value in a parallel job?

I sometimes end up in a situation where:

  • I have a NativeArray that is stored somewhere and is allocated only once at initialization with a “worst case scenario” length
  • I need an IJobParallelFor to write to that array in parallel, with all values packed at the beginning of the array and a counter to keep track of how many elements were added
  • I need another IJobParallelFor to read from that array in parallel (only read the first x indexes, where x is the number of elements added at point 2)

A NativeCounter.Concurrent is perfect for the job of incrementing added element counts in parallel described at point #2, except for one thing: it doesn’t look like there is a way to get its current Count. After a bit of research, I ended up modifying the NativeCounter.Concurrent.Increment() function to return the result of “Interlocked.Increment”, like this:

public int Increment()
{
    // Increment still needs to check for write permissions
    #if ENABLE_UNITY_COLLECTIONS_CHECKS
        AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
    #endif
    // The actual increment is implemented with an atomic since it can be incremented by multiple threads at the same time
    int incrementedValue = Interlocked.Increment(ref *m_Counter);

    return incrementedValue;
}

Now I can get the counter value in my parallel job, and write to the appropriate index in the NativeArray as elements are added. I’d just like to know if this is actually a safe thing to do, and if this return value could be added to the default NativeCounter in the future

Side note: I don’t think I could use a NativeQueue for this scenario instead of a large persistent array and a counter, because it doesn’t look like there’s any way to dequeue a NativeQueue.Concurrent in a parallel job (?)

I had a need for a similar pattern, and solved it like this: Parallel writing followed by parallel reading

this works, but I’m sure all this adding to queues + converting to list + converting to array must be very expensive compared to simply writing and reading to a NativeArray directly

Yeah I also think it could be more elegant, but I couldn’t figure out a better way to do it. I’m still looking for alternative solutions to maybe at least have a comparison point regarding performance for a large number of elements, so keeping my eyes on this thread. :slight_smile:

Reading the value from the NativeCounter like that is generally fine since no-one can be reading at the same time. The NativeCounter was just created as an example of how to write custom NativeContainers more than solving any actual problem so I just kept it as simple as I possibly could.

The only problem I see with using it like you describe is that you will get a lot of cache collisions since all threads are writing to the same cache-line due to interleaved indices in the worst-case sized NativeArray from step 1.

Creating a NativeQueue.ConcurrentRead which allows concurrent dequeue when no one is enquing should be possible and most likely a more performant solution, but I do not know how hard it would be to implement. The internal structures are setup to get good concurrent write performance, they might have to change significantly to also support concurrent read - even if it is not possible to read and write at the same time. I’ll add it to our backlog to investigate.

2 Likes

Hey @timjohansson just curious if there has been any more research done towards the NativeQueue.concurrent dequeue ability, or an approach that would be relatively ideal for performance? :slight_smile:

Unfortunately we have not had time to look at that yet. We do have an issue for NativeQueue.ConcurrentDequeue in our tracking system so it is on our roadmap to investigate, but I cannot say right now when we will look at it.