- Home /
Memory Management and List
Hey all,
Inspired by this question on textures and their memory usage, I decided to spend a little time playing around with Unity and its memory usage, and I found several things that go against my understanding of how this is supposed to work, and frankly, I'm a little disturbed, and hoping someone can help me shed some light on what's going on. O_O
First off, the project is an empty testscene. The only GameObject present is a camera with a script attached. The script looks like this:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class NewBehaviourScript : MonoBehaviour {
List<int> test = new List<int>();
void OnGUI()
{
if (GUILayout.Button("Add items"))
{
for (int i = 0; i < 100000; i++)
{
test.Add(i);
}
Debug.Log(test.Capacity);
}
if (GUILayout.Button("Delete items"))
{
//test = new List<int>();
test.Clear();
test.TrimExcess();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
Debug.Log(test.Capacity);
}
}
}
As you can see, the only thing the script does declare an empty List, then paint two buttons, one that lets me add 100.000 items and another that deletes and trims the list. I have two questions, both of which are memory related:
When I run this scene in a freshly opened editor (Unity Pro 3.4), the total memory usage reported by the profiler stabilizes at 196.5MB (or so). When I stop, and then immediately click Play again, the reported total memory is 197.6. When I stop, then hit play again, it's 199.8. Then 201.2. Then 202.5. I can keep doing this as many times as I want. Why does the editor leak ~1MB memory every time I run a basic program in it? Is this because some left-over data from previous executions in the editor doesn't get garbage collected till much later, or something?
About the list that gets manipulated by the buttons... I can cause the expected jumps in memory usage every time the runtime system doubles the List's capacity to make room for more items. That matches my expectations just fine. But no matter what I do, I can't seem to cause the memory to be freed again. According to MSDN, calling Clear and TrimExcess is supposed to first wipe the list, then set the capacity to the number of items (0, after clear), which reallocates the memory. I.e. if the capacity gets set to 0, it should free the memory previously allocated to List items, right? But it doesn't. I'm not seeing a drop in total memory in the profiler, and I'm not seeing a drop in Windows' Task Manager if I run a build outside the editor, either. Why doesn't TrimExcess free my memory? It makes no difference if I re-instantiate the entire list with the new-keyword. I'm still not getting my memory back. This doesn't make sense to me. :-/
UPDATE: I just tested this in a standard WinForm app with the same two buttons, using Microsoft's compiler and .Net runtime (MS Visual C#). The memory management is correct there. Clear+TrimExcess definitely wipes the list and frees the memory there. This behavior is something Unity/Mono.Net related.
Could also be something to do with log messages etc, I wouldn't rely on editor performance data.
Yes. That's what I meant by "not seeing a drop in Windows' Task $$anonymous$$anager if I run a build outside the editor". ;)
Christian, I'm glad someone else has seen this too, because I thought I was going crazy at first. I had the same issue with large lists of floats and Vector2s. I had a large list that I would populate until I had enough information to create a 65536 vert mesh, then I would instantiate it and move on to the next mesh. After every loop I would reassign to the same list, and I would very quickly get a giant memory leak. I tried clearing and setting null, but nothing helped. Eventually I changed the code to use float[] and Vector2[] ins$$anonymous$$d (there were reasons I didn't initially do this, but I was able to get around them). As soon as I did that, the memory leak disappeared.
BTW: this wasn't just an artifact of the editor or profiler. This would occur even in compiled exe versions, would appear in Windows Task $$anonymous$$anager, and would cause heap errors after a while of use.
Has anyone else noticed that the default namespace for List is Boo.Lang.List?
Is that implemented by $$anonymous$$ono or is this unrelated? The namespace for a List in .NET is System.Collections.Generic.List
Answer by gruhm · Nov 09, 2011 at 08:58 PM
Try adding Debug.Log(System.GC.GetTotalMemory(false));
after you fill the list and again after the clear. This should show you the number of bytes .net thinks are available after each operation.
I'm a little weary of trusting Task Manager as your code is not the only thing going on in that process. So at least this way you can peak in at what the memory manager thinks it has at hand and see the effect of your code on it.
I'm at work or I would test it out myself. BTW: if you change the parm to 'true' it will force a collection prior to reporting size.
Also consider trying it outside of OnGUI, maybe with a couple planes, GUITextures, whatever, to take the GUI system out of the equation.
Answer by artucus · Aug 07, 2011 at 12:16 AM
Christian, I was glad to see someone else was having the same issue as I. Here is what I did (with help) to resolve (I believe) the issue.
public void DeleteAllCubes() {
// At this stage, a grid of 400 cubes has been created via code and each one added to the list “allFloorCubes” as GameObjects
// allFloorCubes currently has a Count of 400 and a Capacity of 512. I think the 512 is memory allocation.
// I want to delete all my prefab cubes on screen…
foreach (GameObject cube in allFloorCubes)
Destroy(cube);
// at this point, I only Destroyed the GameObjects so the grid is gone, I have not touched the list “allFloorCubes”.
// allFloorCubes still has a Count of 400 and a Capacity of 512. I think the 512 is memory allocation.
// now I can do a Clear against the list.
allFloorCubes.Clear();
// allFloorCubes now has a Count of 0 BUT the Capacity is STILL 512
// so it’s like Unity has not released the memory resources for the list, If I rebuild another list the Capacity would double to 1024, ect ect.
allFloorCubes.TrimExcess();
// allFloorCubes now has a Count of 0 AND a Capacity of 0
// the memory allocation has been cleared and there was much rejoicing.
}
// for redundancy or just another way to clear the list and it’s memory allocation… public void InitializeList() {
try
{ allFloorCubes = null;}
catch { Debug.Log("Did not deallocate list 'allFloorCubes'."); }
try
{ allFloorCubes = new List<GameObject>(); }
catch { Debug.Log("Did not create list 'allFloorCubes'."); }
}
Only the OP uses plain integers which aren't Unity Objects and can't be destroyed through Destroy. OP does Clear and TrimExcess as well, so your code shouldn't be any more efficient. If it is, it seems that Unity/$$anonymous$$ono does something funny with a list of integers.
Answer by J3-Gaming · Nov 09, 2011 at 08:24 PM
Would it be possible for you to use another List class?
Define this at the top:
using List = System.Collections.Generic.List<int>;
Then in your code would declare it like this:
private List test = new List();
Does this still provide a memory problem?
Answer by HazeTI · Mar 08, 2012 at 12:13 PM
In regards to your second point, I'm unsure of the behaviour in the editor but on iOS and Android devices any memory that Unity acquires is not released back to the OS even though the memory is freed within Unity.
For example, an app is using 50mb of memory. Then, for whatever reason, it temporarily needs 60mb but then releases those extra 10mb. The memory the Unity app is using stays at 60mb but within that 60mb there is 10mb free to use. Only if the app goes over the 60mb will the memory being used increase, and stay at that level.
Effectively once Unity uses memory it is never released back to the OS until the app exits, but the memory is free within Unity for use later on.
...
At least, as far as I am aware.
Yes, you're describing it exactly right, and this is a very typical symptom of this garbage collector's drawbacks. It's also exactly what happens with a desktop application. What you're seeing occurs because the memory reserved by Unity is fragmented by consecutive allocations and de-allocations of memory, so Unity's runtime system cannot return it to OS. Unity can allocate $$anonymous$$or things itself inside the holes in the heap as long as it finds a gap large enough to fit a new allocation. If it doesn't, it will ask OS for more memory to extend its heap, and then you see the increase from 50 to 60mb.
Answer by roamcel · Aug 05, 2011 at 09:17 AM
Pretty low level and worrying issue. Have you checked if the memory clears correctly after program exit? If the program hogs memory and doesn't release those "1mb" after progam exit, it's definitely a garbage collector bug.
I just tested this. Unity.exe displays the same delta-memory (~1$$anonymous$$B/run) in the Task $$anonymous$$anager in Windows as the profiler does per run. The process (Unity.exe) does not release the memory when I stop running the scene in the editor. Strangely though, the memory usage reported by the profiler in the editor does not match the memory usage reported by Windows Task $$anonymous$$anager for Unity.exe. Where the profiler says 208.5$$anonymous$$B, the Task $$anonymous$$anager says 180.6$$anonymous$$B. The plot thickens...