Search Unity

How to allocate NativeContainer inside long running Job

Discussion in 'Entity Component System' started by MantridJones, Jun 1, 2020.

  1. MantridJones

    MantridJones

    Joined:
    Oct 9, 2014
    Posts:
    20
    Hello,

    INSIDE a long running Job (e.g. 5s), how are we supposed to allocate a NativeContainer for writing?

    -When using Allocator.Temp the Container will be deleted by Unity after a few frames (EDIT: Turns out, it will not be deleted until it is out of use (see post #5)):

    Internal: JobTempAlloc has allocations that are more than 4 frames old - this is not allowed and likely a leak
    Internal: deleting an allocation that is older than its permitted lifetime of 4 frames (age = 7)


    -When using Allocator.TempJob or Allocator.Persistent it says inside a Job, only Allocator.Temp can be used:

    System.InvalidOperationException: Jobs can only create Temp memory


    Then how do I allocate memory for a longer job?

    I know, there is a "workaround" by simply creating a Persistent NativeArray on the main thread and feeding it into the Job. However, imagine using an iJobParallelFor that runs for 1 million iterations. Each iteration requires a NativeArray of 65536 floats to WRITE into. Since parallel job iterations can't write into the same memory, I would be required to allocate a NativeArray of 65536 million floats to feed into the iJobParallelFor. That can't be right.

    Also, since I need the Burst compiler optimization, I can't use managed arrays.

    Is there some workaround I've missed?

    I'm grateful for any suggestions.
     
    Last edited: Jun 1, 2020
  2. Deleted User

    Deleted User

    Guest

    Just allocate the persistent block once and keep reusing it? Don't forget to Dispose it in OnDestroy.
     
  3. MantridJones

    MantridJones

    Joined:
    Oct 9, 2014
    Posts:
    20
    Thank you for your response. Maybe I misunderstood, but I don't think this is possible/feasible. Inside a job you can't allocate persistent NativeContainers. And if you suggest doing it outside of the Job and feeding it in, it will not work for iJobParallelFor since parallel iterations would write to the same memory. So it would not be a 'reusing', as you said, but a 'simultaneous' using, which will lead to wrong values inside the container. Also, as I mentioned, making a persistent container large enough - so that each iteration gets to use its own sub-block of memory - is not an option if the numbers of iterations and the required container-size per iteration is large. It simply becomes too big.
     
  4. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,684
    You can, this is what native collections doing by themself when resizing (For example NativeList in IJob), they reallocate memory with same allocator label (for example if you passed NativeList with Persistent allocator label they allocate new Persistent memory right inside job on resizing container, memcopy old data to newly allocated memory and free old memory block). Also, you can write your native containers\structs where you handle that in a different way, and finally, you can allocate memory manually and pass a pointer to your next operations. The intention of that
    Jobs can only create Temp memory
    is safety, to prevent you from memory leaks. But if you completely understand what you're doing and can handle manual memory management - you can do that, but at your own risk.
    Like we use our own specific set of unsafe containers for different things, like preallocated flow fields which will be used in next jobs where they will be filled and saved as a pointer in flow fields storage map.
    upload_2020-6-1_18-48-38.png
     
    Last edited: Jun 1, 2020
    andrew-lukasik and MantridJones like this.
  5. MantridJones

    MantridJones

    Joined:
    Oct 9, 2014
    Posts:
    20
    Thank you eizenhorn. So if I create my own NativeContainer, I can use my preferred allocator and handle safety myself. That is a really good alternative. For other people who stumble upon this, here is an example from Unity: https://docs.unity3d.com/ScriptRefe...LowLevel.Unsafe.NativeContainerAttribute.html

    Also, during my search for a "just don't mess with my NativeArray you impatient safety algo"-flag, I found that the the flag 'ENABLE_UNITY_COLLECTIONS_CHECKS' is responsible for those "older than 4 frames" messages and that this flag is (apparently) auto-enabled in the editor, but disabled in a build. Finally, when using Allocator.Temp inside a Job (on a build-in NativeContainer) it might say "deleting an allocation that is older than[...]", but that doesn't actually happen before it is no longer in use. Further, that "deleting"-message appears even after you already called Dispose() on the Container in question. That seems a little weird. So in summary, it really doesn't seem to matter if a Temp allocation is used for a long time.
     
  6. Deleted User

    Deleted User

    Guest

    IJobParallelFor provides you with an index that allows you to safely write to the same memory block per thread. You allocate your container outside of the Job if the size is known, if it is unknown just allocate it to handle worst case scenario.

    Then just simply schedule your job passing the container and iterating over it safely. Also I don't really understand why would you need 65536 millions of floats.
     
  7. MantridJones

    MantridJones

    Joined:
    Oct 9, 2014
    Posts:
    20
    I think we are talking about different things. Imagine you are trying to solve a math problem. You have a float as an input and a float as an output. But to calculate the output you need temporary memory to hold 256*256 (i.e. 65536) float values that you need to write into and read from. Lets call this tempMem. Now imagine you want to solve this problem not just for one single float value but you are interested in the solution for many different float values. Lets say you want to know the output of 1 million different input values (independently). To save time it makes sense to utilize iJobParallelFor.
    You are correct that by using iJobParallelFor you get an index to write the output value into a NativeContainer that the main thread will be able to use, once the iJobParallelFor completed. However, I'm not talking about the output container. I'm talking about the 256*256 float values (tempMem) that are required to do the calculation once. Since the 1 million calculations are independent from one another, you do not want two iterations to write to the same tempMem. It would mess up all the calculations. So you need independent memory that can hold 256*256 floats. So if you allocate tempMem outside of the iJobParallelFor, you would need to allocate a huge amount (65536 millions of floats). However, if you allocate the memory inside of the Job's Execute() method, each worker will only allocate 256*256 floats. So you only declare as much as you have workers doing calculations in parallel.

    I know its a bit abstract, but the actual problem involves much more that is not relevant to the issue I was having.

    I just added this explanation to let you know what I meant, since you were kind enough to respond and seemed interested. Have a great day and stay healthy.
     
    Last edited: Jun 1, 2020
    PrimalCoder likes this.