Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Can jobs run independently from the frame?

Discussion in 'Entity Component System' started by Zylkowski_a, Aug 9, 2020.

  1. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    Are all jobs scheduled in one frame needed to complete in the same exact frame? If no, then what happens with native containers passed to them with Allocator.TempJob?
     
    Egad_McDad likes this.
  2. JakHussain

    JakHussain

    Joined:
    Oct 20, 2016
    Posts:
    318
    If my memory serves me correctly, if you create a native container with temp allocation then yes, you can schedule a job and let it run in the background without completing it but after 4 frames Unity will actually dispose of the data for you. You may have noticed you get errors saying a native collection hasn't been disposed resulting in a memory leak but Unity doesn't just leave the memory leak there on its own it's actually disposed of for you.

    This means if your job is still running after 4 frames it'll end up pointing to a deallocated container.

    Your best bet is to use persistent allocation alongside the DeallocateOnJobCompletionAttribute in your job.
     
    Cell-i-Zenit likes this.
  3. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    This explains so much wow ... can you be a bit specific here on how to fix this? I have alot of long running jobs and always wondered why nothing is really working as expected.
     
  4. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    Use Allocator.Persistent if you need to run for more than 4 frames
     
    Nyanpas likes this.
  5. iamarugin

    iamarugin

    Joined:
    Dec 17, 2014
    Posts:
    880
    And do not use JobHandle.CombineDependencies and it will be fine.
     
    Nyanpas likes this.
  6. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    What, why?
     
    Baggers_ likes this.
  7. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    As a side note,
    DeallocateOnJobCompletionAttribute
    seems like very poor design. It's the caller's responsibility to decide if the buffer is still in use or could be freed. You can get the same effect with sticking Dispose at the end of a JobHandle dependency chain. Eg instead of..

    Code (CSharp):
    1. struct MyJob : IJob
    2. {
    3.     [DeallocateObJobCompletion] public NativeArray<int> array;
    4.     // ...
    5. }
    6.  
    7. new MyJob { array = array }.Schedule();
    The equivalent, "cleaner" code is:

    Code (CSharp):
    1. struct MyJob : IJob
    2. {
    3.     public NativeArray<int> array;
    4.     // ...
    5. }
    6.  
    7. JobHandle handle = new MyJob { array = array }.Schedule();
    8. array.Dispose(handle); // array will be disposed as soon as the handle is completed
     
    apkdev, charleshendry, DrViJ and 4 others like this.
  8. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    ok how is DeallocateObJobCompletion going to work?

    My job fills some native arrays over a long period of time.. when the job completes, the arrays are correctly filled and i can check them out. I had set the native arrays already to persistent, but now the native arrays are getting disposed on job completion ...

    so what is the correct way of scheduling jobs long term, then get the data out of the job, then deallocating the whole thing?
     
  9. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    I am doing something similar. Just check if filling process is done, if yes then use filled array and dispose it afterwards.
     
  10. iamarugin

    iamarugin

    Joined:
    Dec 17, 2014
    Posts:
    880
    Because it uses NativeArrays with TempJob allocator under the hood. So after 4 frames you will get a bunch of warnings, that TempJob allocations were older, than 4 frames.
     
    MNNoxMortem likes this.
  11. Zylkowski_a

    Zylkowski_a

    Joined:
    Jul 27, 2019
    Posts:
    157
    So how should i combine dependencies in cases where jobs might run longer than 4 frames?
     
  12. iamarugin

    iamarugin

    Joined:
    Dec 17, 2014
    Posts:
    880
    Only chains of jobs, like:
    Code (CSharp):
    1. var dep2 = job2.Schedule(dep1);
    2. var dep3 = job3.Schedule(dep2);
    I didn't try to passing persistent NativeArray into JobHandle.CombineDependencies, may be it'll work too.
     
  13. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    It not using NativeArrays\Slices by self under the hood, only if you pass them as arguments then their pointer will be used on C++ side. It does not allocate any new native arrays\slices. If you pass handles to CombineDependencies as NativeArray\Slice<JobHandle> after calling CombineDependencies you can (and should) immediately dispose array as combining happens on main thread and this array\slice just utility (which even can and should be allocated with Temp allocator for better performance) and wouldn't be used after calling CombineDependencies and absolutely not related to long-running job or not.
     
    Last edited: Aug 13, 2020
  14. iamarugin

    iamarugin

    Joined:
    Dec 17, 2014
    Posts:
    880
  15. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,683
    2 votes - not a good marker, not even confirmed, I'm 100% sure these 2 people just messed other things not disposed other native arrays. As simple proof - jobs living more than 4 frames - 10 seconds! And this test I running in our production project where many other jobs, systems doing their work and combine dependencies.
    upload_2020-8-14_2-30-30.png
    upload_2020-8-14_2-31-42.png

    Again - CombineDependencies do nothing with native collections itself. Looking at source code - enough to see that. It's only getting read-only pointer from passed array\slice of handles and use handles from an array. Moreover, CombineDependencies doing that right at a time when it's called, it's synchronous main thread call and we safely can dispose allocated by ourselves utility array of handles right after calling CombineDependencies as it's not using for anything after that.
    upload_2020-8-14_2-37-56.png

    upload_2020-8-14_2-38-10.png

    upload_2020-8-14_2-38-23.png
     
    Last edited: Aug 14, 2020
    Nyanpas, MNNoxMortem and DrViJ like this.
  16. iamarugin

    iamarugin

    Joined:
    Dec 17, 2014
    Posts:
    880
    Here is very simple example:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Threading;
    3. using Unity.Jobs;
    4. using UnityEngine;
    5.  
    6. public struct HeavyJob : IJob {
    7.     public void Execute() {
    8.         Thread.Sleep(3000);
    9.     }
    10. }
    11.  
    12.  
    13. public struct Job : IJob {
    14.     public void Execute() {}
    15. }
    16.  
    17.  
    18. public class TestJobs : MonoBehaviour {
    19.     private JobHandle handle;
    20.  
    21.     // Start is called before the first frame update
    22.     void Start() {
    23.         var heavyJob = new HeavyJob().Schedule();
    24.         var job1 = new Job().Schedule(heavyJob);
    25.         var job2 = new Job().Schedule(heavyJob);
    26.         handle = JobHandle.CombineDependencies(job1, job2);
    27.        
    28.         StartCoroutine(Waiting());
    29.     }
    30.  
    31.     private IEnumerator Waiting() {
    32.         yield return new WaitUntil(()=> Time.frameCount > 60);
    33.         handle.Complete();
    34.         Debug.Log("done -y");
    35.     }
    36. }
    37.  
    Console output:
    Code (CSharp):
    1. Internal: JobTempAlloc has allocations that are more than 4 frames old - this is not allowed and likely a leak
    2.  
    3. Internal: deleting an allocation that is older than its permitted lifetime of 4 frames (age = 12)
    4. done -y
    5.  
    Tested on Unity 2019.4.7
    I don't know if this is the case for later versions.
     
    apkdev, Nyanpas and burningmime like this.
  17. iamarugin

    iamarugin

    Joined:
    Dec 17, 2014
    Posts:
    880
  18. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    Yes, i have still the same problem.
     
  19. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    760
    So I'm wondering... if in the first frame say 5 jobs get scheduled, each depending on the previous... if one or more of those jobs are slow, it's possible some or all of them could finish in the next frame... right? Or is there some mechanism that keeps them from spanning frames and must be disabled to allow threads to cross into the next frame?
     
    One1Guy likes this.
  20. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,222
    The only two things that stops jobs from traversing across frames is either that they finish before then, or something on the main thread calls Complete(). If you suspect otherwise, the culprit is probably the later done by some mechanism you aren't aware of but is avoidable to some degree.
     
    One1Guy, bb8_1 and lclemens like this.