Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Is NativeArray slower than regular array? (2018.1.0b6)

Discussion in '2018.1 Beta' started by chanfort, Feb 18, 2018.

  1. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    591
    I was testing the new job system and noticed that calculations with NativeArrays are running slower than the same calculations with regular arrays. For tests I used three cases for velocity calculations:

    1 - jobified IJobParallelFor
    2 - the same as 1 but running single-threaded with positions and velocities stored in NativeArray
    3 - the same as 2 but with regular arrays instead of native arrays.

    The results on my computer for 100k points are:
    1 - 60 FPS; 2 - 34 FPS; 3 - 79 FPS.

    This shows that single-threaded calculations with regular arrays are about 2.3x faster than the same calculations using NativeArray. IJobParallelFor speeds up calculations by 1.8x compared to the single-threaded NativeArray usage. However, the IJobParallelFor speedup seems to be not enough to reach FPS when using regular arrays.

    Do I miss something here?

    The full code is bellow:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Jobs;
    5. using Unity.Collections;
    6.  
    7. public class JobDemo1 : MonoBehaviour {
    8.  
    9.     struct VelocityJob1 : IJobParallelFor
    10.     {
    11.         [ReadOnly]
    12.         public NativeArray<Vector3> velocity;
    13.         public NativeArray<Vector3> position;
    14.         public float deltaTime;
    15.         public void Execute(int i)
    16.         {
    17.             position[i] = position[i] + velocity[i] * deltaTime;
    18.         }
    19.     }
    20.    
    21.     struct VelocityJob2
    22.     {
    23.         [ReadOnly]
    24.         public NativeArray<Vector3> velocity;
    25.         public NativeArray<Vector3> position;
    26.         public float deltaTime;
    27.         public void Execute(int i)
    28.         {
    29.             position[i] = position[i] + velocity[i] * deltaTime;
    30.         }
    31.     }
    32.    
    33.     struct VelocityJob3
    34.     {
    35.         [ReadOnly]
    36.         public Vector3[] velocity;
    37.         public Vector3[] position;
    38.         public float deltaTime;
    39.         public void Execute(int i)
    40.         {
    41.             position[i] = position[i] + velocity[i] * deltaTime;
    42.         }
    43.     }
    44.    
    45.      NativeArray<Vector3> position1;
    46.      NativeArray<Vector3> velocity1;
    47.    
    48.      Vector3[] position3;
    49.      Vector3[] velocity3;
    50.    
    51.      void Start(){
    52.          int n = 100000;
    53.        
    54.          position1 = new NativeArray<Vector3>(n, Allocator.Persistent);
    55.          velocity1 = new NativeArray<Vector3>(n, Allocator.Persistent);
    56.        
    57.          position3 = new Vector3[n];
    58.          velocity3 = new Vector3[n];
    59.        
    60.          for (var i = 0; i < velocity1.Length; i++){
    61.             velocity1[i] = new Vector3(0, 10, 0);
    62.             velocity3[i] = new Vector3(0, 10, 0);
    63.         }
    64.      }
    65.    
    66.    
    67.      int jobMode = 1;
    68.      void Update(){
    69.          if(jobMode == 1){
    70.              Update1();
    71.          }
    72.          else if(jobMode == 2){
    73.              Update2();
    74.          }
    75.          else if(jobMode == 3){
    76.              Update3();
    77.          }
    78.        
    79.        
    80.          if(Input.GetKeyDown(KeyCode.A)){
    81.              jobMode = 1;
    82.              Debug.Log("jobMode "+jobMode);
    83.          }
    84.          else if(Input.GetKeyDown(KeyCode.B)){
    85.              jobMode = 2;
    86.              Debug.Log("jobMode "+jobMode);
    87.          }
    88.          else if(Input.GetKeyDown(KeyCode.C)){
    89.              jobMode = 3;
    90.              Debug.Log("jobMode "+jobMode);
    91.          }
    92.      }
    93.    
    94.     void Update1(){
    95.         int processorCount = System.Environment.ProcessorCount;
    96.  
    97.         var job = new VelocityJob1()
    98.         {
    99.             deltaTime = Time.deltaTime,
    100.             position = position1,
    101.             velocity = velocity1
    102.         };
    103.         JobHandle jobHandle = job.Schedule(position1.Length, processorCount);
    104.         jobHandle.Complete();
    105.     }
    106.    
    107.     void Update2(){
    108.         var job = new VelocityJob2(){
    109.             deltaTime = Time.deltaTime,
    110.             position = position1,
    111.             velocity = velocity1
    112.         };
    113.                
    114.         for(int i=0; i<position1.Length; i++){
    115.             job.Execute(i);
    116.         }
    117.     }
    118.    
    119.     void Update3(){
    120.         var job = new VelocityJob3(){
    121.             deltaTime = Time.deltaTime,
    122.             position = position3,
    123.             velocity = velocity3
    124.         };
    125.        
    126.         for(int i=0; i<position3.Length; i++){
    127.             job.Execute(i);
    128.         }
    129.     }
    130.    
    131.     void OnApplicationQuit(){
    132.         position1.Dispose();
    133.         velocity1.Dispose();
    134.     }
    135.    
    136. }
     
    Creepgin likes this.
  2. james7132

    james7132

    Joined:
    Mar 6, 2015
    Posts:
    110
    Any code compiled with the ENABLE_UNITY_COLLECTIONS_CHECKS define has added checks to make sure that you aren't accessing the array from different jobs at the same time in a way that might be unsafe. This is in addition to the bounds checking that both native and managed arrays do. This is on by default in the Editor.

    I haven't checked, but I think said safety checks (unsure about bounds checking) are disabled in non-development builds. I've personally noted a fairly large speedup when making said builds.
     
  3. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    591
    Ok, you seem to be right. I just build the project with no Development Build mode enabled and here are the results:
    1 - 295 FPS
    2 - 156 FPS
    3 - 169 FPS

    So when build, jobified part is the fastest one. NativeArray is still slightly slower than regular array but I can live with that :) . I am curious isn't there possible to disable ENABLE_UNITY_COLLECTIONS_CHECKS while in Editor? Couldn't find it so far. Though it would be a bit weird that in order to check performance each time would be needed to build the project.
     
  4. superpig

    superpig

    Quis aedificabit ipsos aedificatores? Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,194
    We always recommend measuring performance in an actual build, not in the Editor; in the Editor there's a lot of added overhead that will distort your results, both from things like safety checks and also just from things like needing to render all the Editor windows as well as just the game view.
     
    hippocoder likes this.
  5. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    4,488
    First rule of performance test: always test on the device where you will run it, in the environment, you will run it.
    The editor isn't one.

    On the other hand, these synthetic tests are mostly useless. Maybe in your empty memory the managed array was slightly faster than the native array, but in an application, where you're constantly allocating and disposing things in and from the heap, it quickly becomes more slower. Unless you allocate everything when you start up your application. Which may or may not be feasible for your use case.

    I stopped caring about these synthetic tests a long time ago and just investigate my application instead. Whatever works for you in your environment at fit in your style, choose that one. On the other hand if you choose native array, you may get more stable performance. And the possibility to use the same data structure in single and multi-threaded jobs.
     
  6. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    4,713
    GameDevCouple_I likes this.
  7. GameDevCouple_I

    GameDevCouple_I

    Joined:
    Oct 5, 2013
    Posts:
    1,948
  8. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    591
    Thanks Joachim for the link, now it's more clear on what's going on.

    In a mean time I managed to jobify kdtree neighbour search (1.9x speedup) what shows that even complex recursive trees can be jobified. I think that kdtree is the fastest of neighbour search methods and the new job system with upcoming Burst compiler looks very promising.
     
    hungrybelome likes this.