Search Unity

What Is NativeArray?

Discussion in 'Scripting' started by Manato, Aug 9, 2019.

  1. Manato

    Manato

    Joined:
    Jun 11, 2015
    Posts:
    28
    Hi,
    I read about NativeArray in the documentation and the manual, it is really interesting but I've some questions stuck.

    1. Can I use NativeArray outside of the job system?
    2. Will an instance of NativeArray allocate on the stack?
    3. Are structs boxed in NativeArray?
    4. Are there any restrictions?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,527
    1) Yes... for example NativeArray is used by Texture2D.GetRawTextureData:
    https://docs.unity3d.com/ScriptReference/Texture2D.GetRawTextureData.html

    2) NativeArray is a struct and therefore follows the same memory rules as a struct:
    https://docs.unity3d.com/ScriptReference/Unity.Collections.NativeArray_1.html

    As for allocating on the stack. Well that gets into how structs ACTUALLY behave in memory rather than the over-simplified statement that "structs allocate on the stack". Because technically a struct can be on the heap, especially if it's a field of a class. Anyways though... your generalized question has the answer of "yes, it allocates on the stack".

    A NativeArray is really just a wrapper that points at an array in the "native" code (inside the unity engine itself outside of the mono runtime). And that array is technically in a completely different part of memory... not the stack, nor the heap.

    3) No. The values are stored outside of the mono runtime and its memory and rather instead inside the unity engine's memory (of course il2cpp complicates this memory management). They're being passed back and forth.

    This is why the documentation for this[int] reads:
    https://docs.unity3d.com/ScriptReference/Unity.Collections.NativeArray_1.Index_operator.html

    4) Yes... it only supports structs, not classes. Because it needs to be able to easily translate the memory layout of the data type between native code and C#/mono. Which structs are capable of doing... but classes not so easily (due to the inheritance structure of classes in C#/.net/mono).

    And because the array is in the native/unmanaged memory space... there's overheads that come with that back and forth. Though only minor.

    ...

    Is there a reason you're asking? Because unless you're using the job system, or you're retrieving a NativeArray from unmanaged/native code... you probably don't need this. A regular old C# array should get the job done.

    For example... the reason Texture2D.GetRawTextureData returns a NativeArray is for speed of access. Instead of copying an entire array into C#/mono managed memory (which comes with huge overheads), it's giving you a pointer to the array in native/unmanaged memory. This way you can quickly/efficiently modify textures in memory. Without copying back and forth. You only finally 'Apply' the changes to then push it from RAM to VRAM.

    Otherwise you'd copy an array from unmanaged memory (which is in RAM), to managed memory (which is in another spot in RAM), modify, copy back to unmanaged memory, and then finally apply that which moves it into VRAM. That's a lot of work.

    So basically the NativeArray is allowing you direct access to unmanaged memory which otherwise isn't possible.
     
    Last edited: Aug 9, 2019
  3. Manato

    Manato

    Joined:
    Jun 11, 2015
    Posts:
    28
    Thank you, nailed it perfectly!
    I'm guessing because of the word 'native' that it is the way to use arrays without GC kicking in or it will not be recognized by the GC.
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,527
    Not recognized by the GC, since it's in a completely different memory space that is controlled by the unity engine, rather than the mono engine used for scripting.

    This also means you must manually dispose it to clear up the memory used by it in the unity engine side of things if you allocate one manually. You do so with the 'Dispose' method:
    https://docs.unity3d.com/ScriptReference/Unity.Collections.NativeArray_1.Dispose.html
     
    ivaylo5ev, dimmduh1 and Manato like this.
  5. BullDoze

    BullDoze

    Joined:
    Jun 13, 2018
    Posts:
    14
    @lordofduct(ape?): wow this is the first time I found a sound explanation for the sample code in the "GetRawTextureData". There it says ..your get a pointer to the texture and can change directly..but then you still have to apply??!! Your explanation is a massive help for me - thx for clearing this up...and add this to the manual pls :)
     
    ivaylo5ev and lordofduct like this.
  6. JeremyLvChy

    JeremyLvChy

    Joined:
    Jul 23, 2019
    Posts:
    10
    Nice
     
    SpaceSpecial likes this.
  7. Clonkex

    Clonkex

    Joined:
    Jul 31, 2012
    Posts:
    31
    Is that actually true? Surely it's a managed object and will automatically call dispose in its finalizer.
     
  8. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,979
    No, you have some misconceptions here. First of all Dispose (or IDisposable) is just a convention that is used in a using block. The Dispose method is never called automatically unless it is called implicitly from a destructor or when using a "using" block. However structs can not have a destructor / finalizer at all. So we're back to the case that you have to take care of calling Dispose yourself.

    Note that a NativeArray, as lordofduct explained already, is just a wrapper. It does not contain any data or memory. Instead it contains a handle / pointer to a native C++ object that is created on the C++ side on the native heap. So the actual array is not in managed memory and of course not subject to garbage collection. Structs in C# are just plain data with no inherent logic applied. They don't need to be created nor destroyed. However a NativeArray essentially holds an internal reference to a native objects which would just be lost if you're not disposing it properly.

    You can actually view the managed part of the NativeArray on github. As I already said, structs can not have a finalizer in C#. Since a NativeArray actually represents a classical native C++ array, you really should be careful how to handle those. You can introduce actual memory corruption and memory leaks this way, The NativeArray brings you the benefits of native arrays into the managed world but also brings most of the bad things with it as well. So when using a NativeArray you should have a good reason and use it correctly.

    If you look at the managed source code I've linked above you will notice that the NativeArray contains an actual unsafe memory pointer. This is a "true" memory pointer. The destination of that pointer is not in the managed land.
     
    AshwinMods, ivaylo5ev and lordofduct like this.
  9. Clonkex

    Clonkex

    Joined:
    Jul 31, 2012
    Posts:
    31
    Not really, I just wasn't paying attention and forgot it was a struct and can't have a finalizer. I know how Dispose/IDisposable works. I was assuming that to prevent memory leaks it would have a finalizer that would call Dispose if it hadn't already been called, in case the user forgot to do that. Since I forgot it wasn't a reference type, I was thinking it made no difference what the underlying data was since Dispose would get called when the GC ran and that would handle cleaning up the unmanaged memory.

    Also, IMHO a finalizer should never be called a destructor since it's definitely not a destructor in the traditional sense.
     
    Bunny83 likes this.
  10. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,979
    Sure, that's debatable. Though a destructor and a finalizer both do the same thing as they clean up an object after it is no longer used. It never really "destructs" the object actively.

    As Eric Lippert (one of the leading developers of the C# compiler) pointed out in his blog the granular distinction was a more "recent" distinction / specification. MSDN has just recently renamed the article from destructor to finalizer (was renamed probably around 2021 as you can see due to the history).

    I do agree with you and Eric that it was a bad decision to name it like that, but the reason was most likely that the finalizer uses the exact same syntax as C++ destructors. You will find countless of articles calling it destructor, even recent ones, since that was the officially communicated name for almost two decades. Internally (inside the CLR) it was always called a finalizer.

    Though the difference is quite subtle and is only about the deterministic nature of destructors and the lack thereof when we talk about finalizers. That's also why you generally should avoid using finalizers or at least not rely on them as you suggested. Finalizers may not run at all if the GC does not run. The GC is not required to run after a certain period of time. An application could run for a year straight and the GC could not run a single time. That's why when managing native resources you should always use an explicit way of cleaning up the resources. The Finalizer is essentially just a safety net to not accidentally leak memory. Though it should not be used as the only way to clean up native resources. This would be especially bad for things like file handles or sockets. So even if the NativeArray would be a class (which would be possible of course) you really should not rely on the finalizer. Keep in mind the actual native array is not part of the managed heap. So it's possible that the system runs out of memory (due to a huge amount of native memory allocations) but the managed heap is doing fine and still has room. So the GC may not be triggered and therefore the finalizers would not free up the native memory. Though your app would simply crash because it runs out of native memory, even though it may no longer be needed.

    Anyways, great we cleared up the confusion. For sure it requires a lot of active brain reprogramming to permanently switch to "finalizer" in the future. At least MSDN is now on the track to eliminate the term destructor from the C# docs which should help with the switch. Though old developers for sure will have a hard time to get used to the new terminology. So prepare yourself to see it being called destructor occationally.