How to pass an array to a job

I want to pass an array of precalculated values that i can fill in the start method to a job, something like;

for(int i = 0; i < 5000; i++)    
{
    arrayOfInts[i] = i;
}

then i want to access that array inside the execute method

public void Execute()
{
   if(arrayOfInts[5])
   {
        //do something
   }
}

how to do this?

Use NativeArray for that and pass into your job

Reference types (class) like managed arrays (int[] etc) and List<int> can’t be easily passed to jobs. You should instead use native containers which are supported as fields in jobs. Do something like this:

var array = new NativeArray<int>(5000, Allocator.TempJob);
// prepare data before passing to the job
var jobHandle = new MyJob { Array = array }.Schedule();
var disposalHandle = array.Dispose(jobHandle);


struct MyJob : IJob
{
    [ReadOnly] public NativeArray<int> Array;

    public void Execute()
    {
        // ...
    }
}

Native containers like NativeArray need to be allocated for use and, if the allocator type requires it, disposed once no longer needed to avoid leaks.

that works thanks

Altou checking the array elements to compare has horrible performance, is this normal?

[BurstCompile]
public struct myJob : IJob
{
    
    [NativeDisableContainerSafetyRestriction]
    public NativeArray<int> Array;

    
    public int i;
   
   
    public void Execute()
    {
        
        
        i=0;
        for(int j = 0; j < 500; j++)    
        {
            for(int u = 0; u < 500; u++,i++)    
            {
                
                
                if(Array[i % 100] == 1)
                {
                    //do 
                }
                 
                
            }
        }
        
        
    }    

        
          
                
            
        
    
}

This code takes 5ms, is there a better way if i want to check the elements inside the execute method?
btw is not the mod operand whats causing the problem, is the check of the Array elements, but i dont know why.

For one, in the editor all the security checks are enabled. Profile a build.

You should use an IJobParallelFor if you want multithreading. An IJob runs on just one thread.

Your loop runs a quarter million of operations, and in the inner loop you have a condition which prevents vectorization and then you’re also using the modulo operator which is significantly slower than basic operations like add, sub, mul, div.

The loop can be rewritten to increment u and i by 100 instead, removing the modulo and significantly reducing the number of iterations.

When it comes to performance, it matters a lot how you write the code. :wink:

I have also noticed that it seems that the read and write of NativeArray is much slower than that of ordinary arrays. Even if multithreading is used, the efficiency may only catch up with the access speed of ordinary arrays. Does anyone know the reason?

Most likely reason: profiling in the editor. All safety checks are enabled in the editor unless you explicitly turn them off for the current session. These checks severely impact performance. Also not using the [BurstCompile] attribute will severely degrade performance as no vectorization is performed.

If the test code is similarly minimalistic and artificial as the code above, profiling that is practically meaningless. Be sure to profile code that represents real-world functionality, not code that solely tries to test only collection functionality. There is an abundance of tests like comparing for vs foreach vs while that ignore the fact that the performance of any of these is negligible compared to what the code actually does, and how the compiler may end up optimizing the array or loop when combined with actual code.

You’re right.
The reason I thought about this is that I want to implement a complex algorithm in C++ (integrating it into the Unity source code). So, I first created a demo in C# to validate the performance feasibility. I wanted to compare how much performance improvement I could achieve using the Job System, but as you mentioned, it’s difficult to make a direct comparison since there are too many different optimizations involved.

hi, if it worth something, in my testing i concluded that using if/switches inside a job is not a good idea, while doing it in a task works perfectly fine, doing the same on a job will sucks all the performance, so if u have conditionals using a job might not be a good idea, but im not a pro tester so i could be doing something wrong, try doing if/switches on a job then on a task and see for yourself to compare.
Altou it might be the checking of a native array whats sucking the performance dont quite remember, but doing it on a task with normal arrays was quite faster.

This is because conditionals (typically) break vectorization (Burst optimization).

There are tricks to get around these however. You can use Burst compiler hint attribute to say that a conditional is mostly true/false to make it optimize for a specific use case.

You can also sometimes avoid conditionals altogether merely by rephrasing your code. Sometimes it requires running multiple jobs in a sequence so that one or several iterations over the data can be vectorized, while only one job performs the conditionals. And yes, it can be way, way more efficient to run multiple jobs in sequence rather than a single job that does everything even though it goes against nature on first sight seeing that you are iterating over the same data multiple times but that’s just something the CPU enjoys doing way more over testing conditionals and jumping around in memory.

Without seeing it, I know it’s not faster. :wink:
You haven’t properly optimized your job. Feel free to post your code (task and job) in a new thread and you’ll get plenty of pointers how to optimize. More likely than not, your Task job runs only at 10% if not 1% the speed of a Burst optimized jobified version. Perhaps you didn’t even use the BurstCompile attribute in the first place and that’s all that’s needed to speed it up. Or you profiled only in the editor where safety checks can easily kill most of the performance gains.

hi, i used this code:

[BurstCompile]
public struct myJob : IJob
{
    
    [NativeDisableContainerSafetyRestriction]
    public NativeArray<int> nativeArray;

    
    public int i;
   
   
    public void Execute()
    {
        
        
        i=0;
        for(int j = 0; j < 500; j++)    
        {
            for(int u = 0; u < 500; u++,i++)    
            {
                
                
                if(nativeArray[i] == 1)
                {
                    //do 
                }
                 
                
            }
        }
        
        
    }    


}

where it says //do there’s nothing so its effectively only doing the array check, i put that same for loop on a task and is quite faster, the code is using [BurstCompile].

And here is the task;

await Task.Run(() => {
i=0;
for(int j = 0; j < 500; j++)    
{
    for(int u = 0; u < 500; u++,i++)    
    {
                
                
         if(normalArrayOfInts[i] == 1)
         {
                //do
         }
          
    }
}
});

That task runs quite faster.

To be clear checking the array in the conditional seems to be the problem, u can put the mod back and yul see almost no difference.

To be sure i changed the condition in the job to this as well:

if(2 == 1)
{
      //do
}

this in the job takes 0.7 ms, while the array check in the job takes 5ms.

The reason i put the array like that is to simulate a condition check of an array that u can’t avoid, doing the same thing on a task with a normal array is much faster, so it has to be the array check no?

There are tricks to get around these however. You can use Burst compiler hint attribute to say that a conditional is mostly true/false to make it optimize for a specific use case.

You can also sometimes avoid conditionals altogether merely by rephrasing your code. Sometimes it requires running multiple jobs in a sequence so that one or several iterations over the data can be vectorized, while only one job performs the conditionals. And yes, it can be way, way more efficient to run multiple jobs in sequence rather than a single job that does everything even though it goes against nature on first sight seeing that you are iterating over the same data multiple times but that’s just something the CPU enjoys doing way more over testing conditionals and jumping around in memory.

if the array check is the problem then running multiple jobs would be redundant(?), i mean u have to check the array anyway, on each job, the same amount of times, assuming u can’t avoid the check.

The culprit is likely this kind of staggered loop:

If you rewrite it to do this instead it should be a ton faster:

        for(int i = 0; i < 500 * 500; i++)

Now in the inner loop you can do some math to get actual coordinates. This will still be a lot faster than separately counting j/u (x/y ?) up. Particularly if you make the mistake of disregarding the array memory alignment. If the j elements follow each other but you iterate over the u indexes then you will do something akin to “get value at index 0, get value at index 500, get value at index 1000, …”.

Basically you are offsetting any reads/writes by 500 and thus forcing the CPU to jump around in memory wildly, thrashing the CPU cache. Just iterating over an array in a jagged vs linear way can make a difference upwards of several thousand (!) times faster/slower!

As to math, if you have a flat 2d array with x,y coordinates, then you can calculate the index from x,y as well as calculating the x,y coords from an index so that you only need a single for loop. For example:

var x = 10, y = 20;

// coords to index
var index = y * 500 + x; // equals 10,010

// index to coords
var x1 = index % 500;
var y1 = index / 500;

These safety checks are only done in the editor and debug/develop builds. That’s why it’s important to either disable them temporarily for performance tests or best to profile a release build since that will enable additional code optimizations.

It’s not unusual if a Job runs 10 times faster in a release build than it does in the editor.

hi codesmile, im/was aware of the indexing u listed:

var x = 10, y = 20;

// coords to index
var index = y * 500 + x; // equals 10,010

// index to coords
var x1 = index % 500;
var y1 = index / 500;

im also aware that u can do this in a parallel job with the last index replacing the nested forloop, the reason i didnt use them is to show that the array check was the issue, that said, using the nested loop in a single task is waaay faster, we are talking about less than a ms vs 5ms of the job for the same thing, so i highly doubt it is the nested forloop. Unless u are suggesting to use a parallel for job where in each index of the execute method u only have this line:

 if(nativeArray[i] == 1)
{
     //do 
}

The reason i didn’t put the index was to isolate the check to the array so the index doesn’t matter, doing it with the index doesn’t really change anything since im accessing the correct indexes in secuence using the i variable.

I don’t remember if i have tried doing that jobparallelforl if that is what u are suggesting, altou the logic of it doesn’t make that much sense to me since u would still have to do 500*500 arraychecks(?). So the observation is;

1 task running on 1 tread seems to be much faster for checking an array than 1 job with the same code, the only difference is that the task uses a normal array and the job uses a NativeArray, of the same type, so unless im missing something, using multiple parallel tasks should still be much faster than the parallel job im assuming, for checking an array vs nativearray that is(?)

No, definitely not. Anything you can do with Tasks will be a lot faster with a Burst compiled job, almost guaranteed. I’m guessing you are still basing your assumption on editor-only performance test and/or an unequal comparison, eg leaving some job/burst optimizations untapped.

You should really profile Task vs Jobs performance in a release build. Properly burst compiled a single Job should easily outperform a single background Task, and the same if not more so for parallel jobs / tasks. Primarily because Jobs / Burst do not run safety checks which a Task does, and more so if the Task requires the code to be properly designed with locks and sync points to avoid race conditions, thus stalling the CPU.

Jobs is also much safer to write multithreaded code for since (by default, but overridable) it does not allow you to do the things that could cause race conditions, stalls and such. Note that a race condition rarely causes directly observable issues. More often than not you’ll find some flickering or the random odd value that shouldn’t be or just some parts of the collection seemingly not being processed at all - these bugs are nasty, and intermittently (hard to reproduce). For this reason alone you should prefer to use Jobs.

hi codesmile, i did the test in the build and u were correct, the job now outperforms the task, il keep testing to be sure thanks!