I need multiple IJobParallelFor jobs to write to the same DynamicBuffer, in parallel. Is there any way to do that?
Some details about my case:
All of these jobs are scheduled from the same system.
Contention is guaranteed to be avoided. Each job writes to unique indices of the buffer.
The buffer has been oversized, so that each job will have a cache-line sized block of memory to itself. No false sharing.
I assume that if I schedule two IJobParallelFors, which both have the same type of BufferTypeHandle passed into them, that Unity won’t let them run in parallel.
^ is that a correct assumption? Or will they try to run in parallel, if I don’t explicitly make them dependent on each other?
It’s a little unclear to me, since these are IJobParallelFors - not dealing with any EntityQueries.
Same buffer? Or same buffer type? The former can be done by saving a pointer to the buffer and writing to that directly.
But also, trying to run two parallel jobs in parallel to each other rarely does anything useful unless you have large batches where one job doesn’t use all the threads.
Same buffer. In this particular case, n jobs (could be dozens) can safely write to a set of the same buffers, in parallel. Each jobs writes to unique indices, so contention is avoided - just a consequence of how the logic is organized.
And to your point - the size of the jobs can vary. There would definitely be cases when some threads are free, and parallelism would be a win.
Thanks for this. However, I’m hoping to do this using normal chunk iteration, a la chunk.GetBufferAccessor().
(Trying to avoid going down a rabbit hole with too much detail here): Each job would iterate over a set of chunks, grab the buffer associated with each entity in that chunk, and then write to a unique index of that buffer.
Could the pointer approach you described work for that? So far, I’m not seeing how. Please let me know if I’m missing something.
I know how to pull this off using BufferFromEntity. But since I’ll be iterating over the same entities which own the buffers in question, I’d really rather use regular chunk data access.
It would seem like a let down to have to use BufferFromEntity in this case.
Interlocked? I think I posted an example here (few years back) on a collision test.
You have to test though if it really speeds up things or you cannot better run other independent logic in parallel. @DreamingImLatios likely has better ideas…
you never asked this question. You asked this question:
Simple explanation is probably this:
Each job would iterate over a set of chunks, grab a DynamicBuffer for each entity in that chunk, and then write to a unique index of that buffer associated with that particular job.
No two jobs will ever write to the same index, so it’s safe for many of them to run in parallel.
I’d like to run dozens of these jobs in parallel.
Please let me know if that still doesn’t make sense.
Not sure why you went there. I have tested it before. I am sure. My earlier versions used BufferFromEntitys with [NativeDisableContainerSafetyRestriction] to let multiple jobs write to the same buffer. But that was enough to prove that when these jobs ran in parallel, I was getting a time savings.
Now I’m trying to avoid the cost of using BufferFromEntity, since my jobs will be iterating over chunks which contain these buffers anyway. So why not grab the buffers directly from those chunks, instead of using BufferFromEntity.
Thanks for this explanation:
Your comment makes me think that an assumption I made in my OP is wrong:
I’m under the impression that Unity’s job scheduling system will prevent two jobs that work on the same buffer type from running in parallel. So it wouldn’t matter if you called GetUnsafePtr() on the buffer from inside of a job - by the time you’ve scheduled the job, it would already be too late. Is that wrong?
I’m sorry. But your responses and conclusions do not make sense to me at all.
There’s a difference between a single dynamic buffer instance on a single entity, and accessing multiple instances of the same type. Both questions were meant to identify which is your case, just worded differently since the first time you answered you contradicted yourself.
And your original assumption of using the same write-access type handle in multiple jobs is correct. And your new conclusion is not. That’s why I suggested pointers, because you can capture pointers in one initial job, and use it without the type handle in all the other jobs. Since you claimed access in the system, as long as everything shares the system’s input dependencies and contributes to the system’s output dependencies, it should be fine.
And lastly, unless you were completely disabling safety, you should have gotten errors when trying to use the same BufferFromEntity in multiple jobs running concurrently.
Ok, I think I understand the miscommunication. Thank you for sticking with the thread.
Here’s another attempt at explaining the setup:
I have a large number of entities, each with a DynamicBuffer.
Each buffer is pre-sized, with a capacity of 40.
I have 40 different jobs. Each job is assigned a unique index (0 - 39). Each of these jobs iterates over entities with a DynamicBuffer, and writes to those buffers, using their unique index.
Since no two jobs are assigned the same index, no two jobs will ever write to the same index of these buffers.
I would like these 40 jobs to be able to run in parallel.
If you get your use case to work with BufferFromEntity, and it doesn’t work with BufferTypeHandle, then that’s a bug in Entities. If neither works, there’s an evil pointer trick you can pull off where you write to a readonly pointer obtained from a readonly buffer accessor from a readonly BufferTypeHandle. Then you update the change versions in a separate job (from the same system) using a write-access BufferTypeHandle and just tapping each chunk with it.
I was only able to make it work with BufferFromEntity by using a NativeDisableContainerSafetyRestrictionAttribute to completely disable their safety. I’m not aware of a way to do the same thing with a BufferTypeHandle handle. Do you know of one?
Maybe you could call GetBufferTyoeHandle() just once, and then pass the result into all jobs that you want to run in parallel.
The same attribute should work for BufferTypeHandle as well. If not, that’s a bug somewhere. And yeah. Passing the handle to all the jobs in the system is safe and intended, as long as you don’t do structural changes in the middle of the system (which invalidates them).
Oof…that hurts. But I hear you. Our team is limited by some pretty tight NDAs, so I’m often in here giving vague details, that would sound pretty sketchy to me too. I appreciate anyone who wades through that to give advice anyway.