Search Unity

IJobParallelFor jobs tend to not use the main thread

Discussion in 'Entity Component System' started by alexzzzz, May 21, 2019.

  1. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    I have a bunch of IJobParallelFor jobs (a fluid simulation), where each next job depends on the previous one. Like this:
    Code (CSharp):
    1. var job1 = new Job1 { ... };
    2. var handle = job1.Schedule(count, BATCH_SIZE);
    3.  
    4. var job2 = new Job2 { ... };
    5. handle = job2.Schedule(count, BATCH_SIZE, handle);
    6.  
    7. var job3 = new Job3 { ... };
    8. handle = job3.Schedule(count, BATCH_SIZE, handle);
    9.  
    10. var job4 = new Job4 { ... };
    11. handle = job4.Schedule(count, BATCH_SIZE, handle);
    12.  
    13. var job5 = new Job5 { ... };
    14. handle = job5.Schedule(count, BATCH_SIZE, handle);
    15.  
    16. handle.Complete();
    I've noticed that most of the time only the fist job uses all the 4 cores my CPU has. The second job prefers to use 3 cores (but sometimes uses 4), the other jobs use 3 cores almost always, while the main thread is waiting doing nothing.

    Tested in Unity 2019.1, both editor and dev builds.

    Common case:
    1.png

    Less common:
    2.png

    Very rare (two final jobs still run on 3 cores, but I don't care since they are very short):
    3.png

    The empirical rule is the following: When handle.Complete() is called, the first of the jobs, that have been scheduled by that time, tend to use all the threads, and the following jobs tend to not occupy the main thread (they still may use it, but with rapidly decreasing probability).

    If I want all the jobs to use all the threads all the time, I have to "complete" each job right after it has been scheduled, introducing extra synchronization points. It doesn't matter in this particular case, but still the solution is neither elegant nor obvious. Are there any other solution to force the jobs to use all the cores?
     
    Last edited: May 21, 2019
  2. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Are you sure this isn't just a case of the OS decides to give the core to another process?
     
  3. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Doesn't look so.

    However, I'm not sure the modern task manager can be trusted. On the first picture it shows that 1/4 of my quad-core CPU is idle. On the second picture it shows that almost 100% of CPU is occupied by Unity Editor process.

    By the way, I set the process priority from normal to high to see if it would change anything. It didn't.

    taskmanager.png
    taskmanager2.png

    PS
    On standalone builds (both dev and normal) the task manager shows the same, the process takes about 75% of the CPU and the rest 25% is idle.

    PPS
    Here is the build: https://dl.dropbox.com/s/flay945gn4ifwpr/Fluids.zip (Win x64)
    I would expect it to consume all the CPU available, but it doesn't. In case of a Threadripper, you can always add more particles ("1" - remove 1000 particles, "2" - add 1000 particles).
     
    Last edited: May 23, 2019
  4. JakeTurner

    JakeTurner

    Unity Technologies

    Joined:
    Aug 12, 2015
    Posts:
    137
    Hi.

    I will do internal checking.
    Just to add it looks like the Unity Job System has 3 worker threads and they are running at capacity.

    I think the interesting question is the main thread waiting for the DensityPressureJob and not running running any other jobs and not helping out.

    Could you explain in a bit more detail what values you have for "count" and "BATCH_SIZE"
     
  5. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    count is the amount of particles in my simulation. The last two screenshots were taken with count = 40000 or something like that. On the screenshots in the original post the value, I think, was about 10000.

    The BATCH_SIZE values I have tried are from 64 to 256. There is no noticeable difference.
     
    Last edited: Jun 18, 2019
  6. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Here is a simple repro:

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Jobs;
    3. using UnityEngine;
    4.  
    5. public class Test : MonoBehaviour
    6. {
    7.     private NativeArray<float> array;
    8.  
    9.     private void OnEnable()
    10.     {
    11.         array = new NativeArray<float>(100_000, Allocator.Persistent);
    12.     }
    13.  
    14.     private void OnDisable()
    15.     {
    16.         array.Dispose();
    17.     }
    18.  
    19.     private void Update()
    20.     {
    21.         const int BATCH_SIZE = 200;
    22.  
    23.         var job1 = new DummyJob { array = array };
    24.         var handle = job1.Schedule(array.Length, BATCH_SIZE);
    25.  
    26.         var job2 = new DummyJob { array = array };
    27.         handle = job2.Schedule(array.Length, BATCH_SIZE, handle);
    28.  
    29.         var job3 = new DummyJob { array = array };
    30.         handle = job3.Schedule(array.Length, BATCH_SIZE, handle);
    31.  
    32.         var job4 = new DummyJob { array = array };
    33.         handle = job4.Schedule(array.Length, BATCH_SIZE, handle);
    34.      
    35.         var job5 = new DummyJob { array = array };
    36.         handle = job5.Schedule(array.Length, BATCH_SIZE, handle);
    37.  
    38.         handle.Complete();
    39.     }
    40. }
    41.  
    42. public struct DummyJob : IJobParallelFor
    43. {
    44.     public NativeArray<float> array;
    45.  
    46.     public void Execute(int index)
    47.     {
    48.         array[index] = Mathf.Sqrt(array[index]);
    49.     }
    50. }
    Most of the time the profiler shows this:

    repro.PNG
     

    Attached Files:

    Timboc, Shinyclef and rz_0lento like this.
  7. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    505
  8. JakeTurner

    JakeTurner

    Unity Technologies

    Joined:
    Aug 12, 2015
    Posts:
    137
    Just a short update. I have recreated the problem locally and will start the investigation
     
    Shinyclef likes this.
  9. JakeTurner

    JakeTurner

    Unity Technologies

    Joined:
    Aug 12, 2015
    Posts:
    137
    There is not a bug in the code. The code was behaving as expected however in this precise scenario this was not ideal and lead to the main thread being idle.

    I have a very quick local change to Unity JobQueue to support this scenario.

    Please ignore the absolute timings - this is debug build.

    The key takeaway is the main thread is now running more C# Jobs than it was before.

    It will take time to implement the full solution. I will try to get the change made to land in Unity 2019.3

    Before
    before.png
    After Screenshot 2019-06-21 at 11.45.32.png
     
  10. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    505
    Well whether it's classified as a bug or not is of less interest to me, I would just say that the 'after' pic is a very sexy core utilisation result :D! I will definitely be keeping my eye out on alpha releases for this change! I think it's going to give a lot of people who have well-jobified code a very welcome performance boost!

    Thanks for the improvement!
     
    chadfranklin47 and fherbst like this.
  11. JakeTurner

    JakeTurner

    Unity Technologies

    Joined:
    Aug 12, 2015
    Posts:
    137
    Just to let you know the code change has landed internally and should be available in 2019.3.0a10 (when it is released).

    Please reach out to me with any further questions
     
    Shinyclef likes this.
  12. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    857
    Will this change eventually be backported to 19.1/19.2?
     
  13. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    505
    Alpha 10 has landed!
    "Kernel: Improve main thread utilisation for IJobParallelFor jobs"

    I just left for work so can't test now. But looking forward to seeing the difference in my own project.
     
    June1111 likes this.
  14. JakeTurner

    JakeTurner

    Unity Technologies

    Joined:
    Aug 12, 2015
    Posts:
    137
    It could be but no plans to do that right now. If you have a need for it then I think 2019.2 is achievable. I think now that 2019.2 is released then 2019.1 will be locked to changes like this.
     
  15. Lynxed

    Lynxed

    Joined:
    Dec 9, 2012
    Posts:
    121
    Can we have a way to explicitly state, that we want the job to be working NOT on main thread? Long background jobs is a thing in my project. If Job Scheduler will decide to throw such a thing onto main thread, there will be hell of a a hiccup.
     
    chadfranklin47 likes this.
  16. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    It should only throw work on the main thread if the main thread is locked waiting for jobs to complete.
     
    Lynxed likes this.
  17. JakeTurner

    JakeTurner

    Unity Technologies

    Joined:
    Aug 12, 2015
    Posts:
    137
    I think this would need to added/supported as a feature. The underlying native system will pick and run any scheduled job potentially on any thread.

    Long running background jobs could cause the hiccups you are concerned about
     
  18. Soaryn

    Soaryn

    Joined:
    Apr 17, 2015
    Posts:
    328
    Just noticed this thread after posting a new one regarding long jobs. Yep! They definitely cause hiccups :(
    Would definitely love a way to add a note to the scheduler "Hey, don't run this on main"
     
    JesOb likes this.
  19. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    But hey, jobs don't run on the main thread, unless you explicitly ask them to, calling jobHandle.Complete().
     
  20. Soaryn

    Soaryn

    Joined:
    Apr 17, 2015
    Posts:
    328
    MMmm the profiler says differently.

    Edit: I think I know why it is now: 2019.3a11
     
  21. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Jobs should only run on main thread if the main thread is locked waiting for the job to complete due to dependencies.

    You can force this by calling Complete() but the dependency system may eventually require certain jobs to complete before a system can run etc.

    If I simply schedule a long running IJobParallelFor with no dependencies on it then it won't ever run on main thread. This is easily shown calculating a bunch of prime numbers.

    upload_2019-8-20_11-0-52.png

    Never touches main thread. Maxing my cpu usage but I'm still pumping out 100s of fps.

    Now the issue is all your worker threads are at capacity so if I wanted to schedule any other job it will have to be scheduled on main thread. So if you're running a multi frame job I'd highly recommend keeping it to a single thread.
     

    Attached Files:

    florianhanke likes this.
  22. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Your last paragraph.

    Not sure if this is possible, but if the scheduler could accept a prio (ASAP vs long running) and then only run the long running, when not busy with ASAP
     
  23. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    This could be a very nice feature.
     
  24. Soaryn

    Soaryn

    Joined:
    Apr 17, 2015
    Posts:
    328
    Uhm, no. :( Even without dependency locking, my job is still being sent to main thread periodically. There needs to be a way to specify if a job should be allowed to run on main thread instead of hoping magic will suffice the scheduler :p
     
  25. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Show us your code because as I said and demonstrated it seems to work for me fine
     
  26. Muhammad_Taha

    Muhammad_Taha

    Joined:
    Jun 21, 2015
    Posts:
    22
    Hi guys. I am facing a kind of same issue. I am scheduling collision Jobs and simple IJob, with a schedule call but they are running on main thread. Aren't they need to be run on worker thread?
    Currently I don't have screenshot of profiler but I will share it ASAP.

    Edit:
    The Default World Collision is the system and it is running on main thread.
    Screenshot 2020-10-12 at 9.33.20 AM.png
    Code (CSharp):
    1. [BurstCompile]
    2.     struct CollisionFall : ICollisionEventsJobBase
    3.     {
    4.         public ComponentDataFromEntity<BucketComponent> bucket;
    5.         public ComponentDataFromEntity<BlastComponent> component;
    6.         public ComponentDataFromEntity<PhysicsGravityFactor> gravity;
    7.         public ComponentDataFromEntity<PhysicsMassOverride> physicMass;
    8.         public BufferFromEntity<BufferComponent> buffer;
    9.  
    10.         public void Execute(CollisionEvent collisionEvent)
    11.         {
    12.             Entity entityA = collisionEvent.EntityA;
    13.             Entity entityB = collisionEvent.EntityB;
    14.  
    15.             if (component.HasComponent(entityA) && bucket.HasComponent(entityB))
    16.             {
    17.                 if (!component[entityA].isBlasted)
    18.                 {
    19.                     physicMass[entityA] = new PhysicsMassOverride
    20.                     {
    21.                         IsKinematic = 0
    22.                     };
    23.  
    24.                     gravity[entityA] = new PhysicsGravityFactor
    25.                     {
    26.                         Value = 1
    27.                     };
    28.  
    29.                     BlastComponent blastComponent = component[entityA];
    30.                     blastComponent.isBlasted = true;
    31.                     component[entityA] = blastComponent;
    32.                 }
    33.             }
    34.  
    35.             if (component.HasComponent(entityB) && bucket.HasComponent(entityA))
    36.             {
    37.                 if (!component[entityB].isBlasted)
    38.                 {
    39.                     physicMass[entityB] = new PhysicsMassOverride
    40.                     {
    41.                         IsKinematic = 0
    42.                     };
    43.                     gravity[entityB] = new PhysicsGravityFactor
    44.                     {
    45.                         Value = 1
    46.                     };
    47.                     component[entityA] = new BlastComponent
    48.                     {
    49.                         isBlasted = true
    50.                     };
    51.                 }
    52.             }
    53.         }
    54.     }
    This is the collision Job

    Code (CSharp):
    1.   protected override JobHandle OnUpdate(JobHandle inputDeps)
    2.     {
    3.         var job = new CollisionFall();
    4.         job.component = GetComponentDataFromEntity<BlastComponent>(false);
    5.         job.bucket = GetComponentDataFromEntity<BucketComponent>(false);
    6.         job.gravity = GetComponentDataFromEntity<PhysicsGravityFactor>(false);
    7.         job.physicMass = GetComponentDataFromEntity<PhysicsMassOverride>(false);
    8.  
    9.         JobHandle jobHandle = job.Schedule(m_stepPhysicsWorld.Simulation, ref m_BuildPhysicsWorld.PhysicsWorld, inputDeps);
    10. return jobHandle;
    My Main System
     
    Last edited: Oct 12, 2020
  27. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
    If you call Complete() early whatever left of the job will complete on the main thread. I have set up all my "long" jobs to use the IsComplete-check and will at the earliest complete on the next frame. I don't know if ECS/DOTS have a concept of a LateUpdate() so that's why I am doing the check.
     
  28. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    505
    This is also a 1 year necro as well as a double post. Your new thread was sufficient, not to mention your claim that you are facing a "kind of same issue" is not true. This thread was about poor utilisation of the main thread while waiting for jobs completion despite many jobs still waiting to be executed, while you are asking about how to schedule jobs on worker threads.

    I'm not typically a necro cop but this is a little silly ^^;
     
    MNNoxMortem and Baggers_ like this.
  29. Muhammad_Taha

    Muhammad_Taha

    Joined:
    Jun 21, 2015
    Posts:
    22
    I thought it's related to this post rather than creating a new thread, after that I thought I should create a new thread as well.
     
    Last edited: Oct 12, 2020