Search Unity

Large Arrays cause "Out of Memory" Error

Discussion in 'Scripting' started by Lizzard4000, Oct 12, 2015.

  1. Lizzard4000

    Lizzard4000

    Joined:
    Mar 3, 2010
    Posts:
    101
    Hello!

    I need to create a lot of Arrays like this:
    var ArrayOfColors : Color[,] = new Color[2048,2048];

    After i'm done with it, i try to free the memory with:
    ArrayOfColors = null;
    and
    System.GC.Collect();

    But i can see that the game still increases memory usage, in the Win Task Manager.

    I tried to find an answer and found this:
    http://answers.unity3d.com/questions/225460/big-arrays-are-never-garbage-collected-why.html
    But i have no idea how to use his solution.

    I'm on Unity 4.6.

    What can i do to free up memory?
     
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Check the profiler for actual memory use. The Windows task manager window measures how much memory is allocated to the program. This allocation doesn't always match what's actually in use.
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    How many is a lot of arrays?

    I actually recently had a discussion on here about this similar situation, I don't remember where it is though.

    Anyways... first and foremost, like @BoredMormon said, check your actual memory usage in the unity profiler to see where the memory is coming from.

    But with that said, you will notice that the memory used by the application in the task manager is still very large. Taking memory away from other programs. Well, there's a reason for that.

    Unity runs all your program code in a virtual runtime called 'Mono', which is just an open-source version of Microsoft .Net. One of its features is that it performs all the memory management for you. Here's the thing though, memory allocation is an annoying complicated task that compilers/languages have been trying to hide from programmers for years.

    When you create a new object (like an array) memory has to be allocated. A program when it first starts will make a guess at how much memory it'll need at startup and ask the OS for a chunk of memory at start to use. In our case, this is called the 'Heap Memory'. As the program runs we toss objects into the heap in the first open spot, it doesn't want to waste time calculating the best packing every allocation, so it will just search for the first open space with enough contiguous area to fit your object. In the case of an array this is BIG.

    So, if we've filled up this memory, and we go to create an object that's really large, and it can't find a contiguous block of memory that it had allocated from the OS already... well it doesn't have enough memory. So it must go back to the OS and ask for another chunk of memory to use for the heap. This is expensive, so our virtual runtime Mono/.Net will assign a larger chunk of data than what we need in anticipation of future objects (usually, depends on how smart the runtime is, us developers don't control that, it's all on the version of Mono/.Net being used).

    This causes the task in Task Manager to look like more memory is being used. Because it is, we've allocated more memory from the OS. That's what that number reflects... it doesn't know what part of that allocated memory is used for anything of substance by the program... it's just been reserved by our program for use whenever it needs it.

    For it to be returned, our program has to return it. Regular GC doesn't necessarily do this, allocating memory from the OS is expensive, so usually it just unassigns the memory, but keeps it reserved for reuse later on. The mono/.net runtime assumes that if you needed this memory at some point in your program, you'll probably need it again.

    It may return it after some time, if it sees you haven't been using it for a while. But I'm willing to bet the old outdated version of Mono that we use doesn't do that. It probably just keeps it and keeps on trucking along.




    This isn't even unique to managed languages either. Unmanaged C++ code is also susceptible to it. Default compiler settings often have the free/delete functions just make it available for reuse, yet still reserved from the OS. This is why programs like your browser if left running for days on end will chew up more and more memory despite the resources it allocated memory for are no longer in use (images and what not). It's not actually using the hundreds of megs of memory it seems to have in the 'task manageer', yet it still has them reserved.




    Your best bet in general is to reuse large data structures as much as possible. You have several arrays that are 2048x2048 in size? If those were just integers... that's 2048 * 2048 * 4 bytes == 16 megabytes of memory, if it were Vector3's for verts, we're now up to 48 MBs.

    Well, if you have several of them, and you create multiple of these arrays, that's 16 megs each time. But if they all are 2048x2048, and I highly doubt you need each array to exist simultaneously. I'm willing to bet you're building some structure, manipulating it, and than dumping it back out again (since you force GC when all done with), create 1 array, do your job, and then fill that same array with the values for the next job. You can process 1000 whatevers (image data maybe?), one after the other, while consistantly using only 16 megs of ram for the entire process.
     
  4. Lizzard4000

    Lizzard4000

    Joined:
    Mar 3, 2010
    Posts:
    101
    Thanks "lordofduct" for that informative answer!

    I guess i was a bit tired yesterday, i can absolutely reuse one array. This solves my issue.

    But the question still remains.
    If you create a big array in the "Start" function, even though it is never used again, it remains in the memory it seems.
    So if you would have to create multiple arrays, then there would be no way to free the memory?
    Just out of curiosity, is there a solution for this?

    Thanks!
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    The array gets removed, as long as you orphan it.

    It's just the memory where the array once was is now still reserved by your program.

    The solution is better design where very large arrays aren't needed all over your program simultaneously and disparate from one another. For instance you could use the factory pattern, so that every place that needs to create whatever you're creating, instead accesses the factory and has the factory create it for them. The factory then maintains a single array for reuse.
     
    Kiwasi and Ironmax like this.