- Home /
C#/Unity Stack size and memory leaks
Hello everybody !
I would like to have a few advices on how to fix a problem I already have searched a lot about. Any help would be very appreciated :)
I'm trying to implement the marching cubes algorithm in C# in Unity, running on grid of size 128**3. The calculation must be done at runtime and has to be instantaneous. I'm developing a toolbox to visualize scientific/molecular data, so I have to generate a lot of surfaces on user demand. To obtain a fast calculation I implemented a recursive version of the algorithm. That's ok the calculation is always done < 30-40 ms even on ~old computer.
BUT I have 2 problems :
Insane memory leaks, due to the memory I have to use during each run of marching cubes and it is obviously not released/freed
Stack overflow exceptions, maybe due to stack size restrictions or wrong auto adaptation, but I HAVE to do this using recursion and have a large stack size
1) Memory leaks
I have a class MarchingCubesRec.cs that executes the work and call another script to generate the final GameObject(s) using the generated mesh. I'm calling it like this :
Main Script
MarchingCubesRec MCInstance; // global
MCInstance = new MarchingCubesRec(); // at start
MarchingCubesRec.MCRecMain(my_arguments); // in function, on user demand
MarchingCubesRec.MCRecMain
GenerateMesh GMInstance; // global
GMInstance = new GenerateMesh(); // at MCRecMain call
// Do the mesh calculation, using only global variables initialized at function call
GenerateMesh.GM(my_other_arguments); // when mesh calculation finished, GM does GameObject(s) construction
//variables are used the same way than the 2 scripts before
That's the way I build each GameObject composing my surface. I'm new to C# and I don't really know if it's the good way to call functions. I also tried static types for both variables and/or functions and other organizations of my code, but each time I generate a surface and create a GameObject my memory is increasing until I get an Out of memory Exception. I have to precise that only one surface is displayed at a time and it never takes more than 5-10 GameObjects. Previously generated GameObjects are destroyed using Destroy() at the same time than the new ones are created.
Does anybody knows WHY my memory is never freed ? In fact I seems that 5-10% of allocated space is freed, from times to times. But I always end up with a few GB of memory used within a short time of utilization. I read a bit about the garbage collector handling of memory and I understood that recursion can sometimes prevent a normal (="efficient" ?) garbage collection. Then, does anybody knows HOW am I censed to handle this ?
2) Stack overflow exceptions
To perform my mesh calculation I use a recursive version of the marching cubes algorithm. My grid size is 128**3 (~2 millions points), so I understood that it's normal to have a large stack size for this kind of thing. I'm sure my recursion is not infinite. The problem is I tried to use the Thread class to force a stack size that fits my needs, but I couldn't generate the mesh while the new thread is not the main thread. Next step I want to try is recuperate the arguments from the calculation thread and then generate mesh on main thread. Do you think that could solve my stack overflow exception problem ?
Thanks a lot for your help, you're awesome, and I've been struggling on this for really too long :(
Hard to say without seeing more of your code. It would appear that you are holding references to something beyond the execution of your routine if the Garbage Collector is releasing 10% - the 90% is still thought to be necessary.
I may be holding references as you say, but I don't understand how :P I 'll post my code soon.
Thanks for your answer !
Do you use Update() or a Coroutine to run your marching cube algorithm, or is it all done in a "single frame", so that the Editor freezes while your algorithm is working? Destroy() will not actually free any memory until the next frame, so you could try DestroyImmediate() ins$$anonymous$$d.
I use OnGUi () and if(GUI.changed) to see if my user wants to see something different, then I generate it. I think the editor is freezing while the algorithm is working, but the calculation is almost instantaneous. I have tried DestroyImmediate but it didn't change anything.
Answer by amirabiri · Jun 15, 2012 at 03:11 PM
Wow, you've stretched both Unity and C# to their limits... well done! :-)
Memory
First thing to do is call the GC yourself every let's say 10 or 20 frames and see what happens:
If the memory problem persists, that means the problem is in your code - you are keeping references to objects somewhere so the GC can't reclaim them. The only solution here is to go over the code with a fine comb, isolate individual parts of the code and test them individually, and so on until you find the leak.
If the memory problem goes away, that means you are just creating a lot of objects, faster than the GC can reclaim (this is very unlikely tough). Solution to this is to go over your code and see where you can try and reuse memory. for example use a List instead of allocating arrays, give it a big seed and reuse it. etc.
Generally speaking memory and GC in the C# env is a big topic in itself and I suggest you seek further help on this topic outside the Unity community as this is a general problem. You can get a lot of help on Stack Overflow, and picking up a good book on C# advanced topics will also help you.
One thing worth mentioning - bear in mind that a "destroyed" object from Unity's point of view isn't "destroyed" from C# point of view! Also unity overloads the == operator to return null if an object is destroyed in unity (but the references still exists in C#). Watch out for that.
Stack Overflow
There is less I can think of here, I've never had to deal with huge stack algorithms. The only thing I can think of is "are you sure you need recursion?". Would it possible to implement the algorithm with Stack or a Queue instead?
Thanks for your answer !
I already tried to force the garbage collection but that didn't change anything :( I'm almost sure I don't use the appropriate data type or structure for this kind of algorithm, but I haven't found it yet.
About stack overflow, yes I'm sure I need recursion because this is the main optimization that allows me to obtain a fast enough calculation. I have looked Stack and Queue but that doesn't seem appropriate.
Well you must be able to use Stack because it is a stack and that's what you are using with recursion :) You would just be writing code without pushing stack frames for the actual routines which would save some memory and that memory is allocated on the heap and not the stack.
Ok, ok, doesn't seem so inappropriate x) I wasn't aware of it. I'll try to adapt my implementation. I have posted a bit more of my code (awaiting moderation) but it's not the part of calculation using recursion.
Would it be possible to have a Stack ? I'll add my actual recursion code very soon, if you would have any more advices on how to adapt it you are so so welcome :)
Not every recursive algorithm can easily be translated to an iterative one with a stack, but many of them are. The key is if the outer execution really needs the inner call to finish in the middle of what it is doing or not.
For example, a directory scan can be translated to an iterative process because usually the processing of the parent and the processing of the children are separate:
RecursiveDirectoryScan(Dir dir)
{
foreach (var child in dir.Children)
RecursiveDirectoryScan(child);
// process current dir ...
}
The above is easily translatable to:
IterativeDirectoryScan(Dir root)
{
var todo = new Stack<Dir>();
todo.Push(root);
while (!todo.Empty)
{
var dir = todo.Pop();
foreach (var child in dir.Children)
todo.Push(child);
// process current dir ...
}
}
However if the outer part relies on the result of the inner part then it becomes much harder to translate. This is where recursion really shines since the platform "freezes" the outer scope for you easily. If you tried to do it manually, you would need to somehow fake that using local variables.
Every recursion can be implemented as iteration and reverse. In some cases it's difficult in some it's quite easy. Recursion has some disadvantages:
function calls are slow and hard to optimise for branch prediction on the CPU.
each function call generates a stackframe which holds all local variables and the return address. $$anonymous$$ost the time you store too much information that way.
Iterative solutions can be quite faster, but in some cases it's difficult to implement them. You need to store all your data manually, but in your own optimised way.
Answer by Munken · Jun 26, 2012 at 10:14 AM
Hello
I was faced with the problem of stack overflow too and the solution was to rewrite the marcher as a iterative algorithm. This can easily be done with a queue. I have implemented the main loop as it (in Java) as
addStartCells(startCell);
while (cells.size() > 0) {
// Next cell to process
MarchCell next = cells.remove(0);
// How many triangles do we need for this cell
int nTriangles = MARCHER.marchCube(next, newTriangles, ISOLEVEL);
if (nTriangles > 0) {
addTriangles(nTriangles, newTriangles);
// Add the triangles from this cell and add neighbors
marchNext(next);
}
}
Then your marchNext method only need to know how to add the neighbors of the the current cube and add them to the queue.
This can also easily be translated into a multithreaded algorithm.
Answer by jc_lvngstn · Jun 15, 2012 at 04:11 PM
Some thoughts:
Make sure you separate out your data generation/storage from your Unity mesh generation and gameobject creation. I'm assuming you are storing your data in a 128x128x128 array. When you create the actual internal data values, run that over and over and see if that is causing an increasing memory usage. This will tell you if your data generation has problems. Now...I have no idea what your data gen and storage looks like, but if it is just calculating and storing values in the array, seems like that wouldn't be the most likely suspect for your memory usage going up and up and up.
Unless you have a 128x128x128 array of classes or structures, and you are recreating instances of those for EACH element of the array, each time you recalculate the data, instead of just reusing them. With that much data...I would reuse them, bigtime.
Anyway...let's say you can just run the part of your code that creates the data, many times for different data, and your memory usage is fine. Then that tells me it's something in the Unity gameobject/mesh creation.
So I would next isolate the part of your code that generates the actual mesh data (vertices, colors, etc). Rerun the entire process several times (in a loop maybe), and see if your memory problems start up. Don't actually create the gameobject yet. This is just to make sure that generating the vertex and other data for the mesh is not the problem.
If memory is still behaving...I would think it was a problem with Unity's way of handling gameobjects. You may have to just reuse them, instead of destroying them and recreating them over and over. I don't understand much about how unity handles memory...but in my experience...it doesn't seem to handle it well.
Anyway, hope this ramble is of some use. Heck, it may even be somewhat accurate :)
I have tried to separate each step, since I wanted the gameobject calculation and generation to be reusable many times. I'm calling my data generation only one time, so it must be leaking on data used during mesh calculation or on gameobject generation.
I'll post more of my code very soon.
I'll also post my final marching cubes algorithm when it will be as robust as I want it to be, since I haven't found any correct C# fast enough implementation.
And thanks, this ramble is of some use !
Answer by JeanClaude2010 · Jun 19, 2012 at 03:35 PM
So here is the full thing, lighted as I can.
Memory problem
I want to display different molecular models one after another so it's something like playing an animation using several frames. At first start I load all the models, which are flattened arrays density maps of 128x128x128. So with my test data (11 density maps) the minimum size I have to load is already 128*3 11 * 4 bytes =~ 93 MB. Each model is 8MB and I'm using ~30 MB of variables when I generate a surface from each file, using my implementation of marching cubes algorithm. It's really rare when memory drops a bit, from something like 10% and it's happening really rarely. It usually grows from several MB at each model generation.
Can somebody tell me how am I censed to have my variables being REUSED EACH TIME I call the method, or to have this class being instantiated a UNIQUE time ?
Stack overflow
When I generate the mesh using recursion, I have a large stack size that sometimes generate stack overflow errors. I tried to force a stack size using Thread overloaded method, but now I can't generate gameObjects in this Thread, this kind of manipulation has to be done in the main thread. Should I recuperate the data from the thread and generate the mesh in the main thread ? (what I assume I can do if I generate the mesh in my main program script). Or maybe there are easier/nicer ways to do this ?
Main script
using UnityEngine;
using System;
using System.Collections;
using MarchingCubes;
class MainProg : MonoBehaviour {
private ImportSPIDER ImportSPIDERInstance; // global instantiation
private MarchingCubesRec MCInstance;
private int[] NZ; // dimensions of each density grid
private int[] NY;
private int[] NX;
private Vector4[] pointstmp; // whole file values, temporary for one file
private Vector4[][] points; // whole files values storage
private string[] filePath = {"frame0_lp10A", "frame4_lp10A", "frame7_lp10A", "frame10_lp10A"};
private int currentFile;
// + other variables
void Start () {
...
nbImportedFiles = filePath.Length; // get number of imported files
ImportSPIDERInstance = new ImportSPIDER(); // initialize SPIDER import instance
MCInstance = new MarchingCubesRec();
for (int i=0; i < nbImportedFiles; i++) { // import all of the files determined by user
ImportSPIDERInstance.Import(filePath[i], out NZtmp, out NYtmp, out NXtmp, out pointstmp); // import SPIDER file
points[i] = new Vector4[pointstmp.Length];
NZ[i] = NZtmp;
NY[i] = NYtmp;
NX[i] = NXtmp;
for (int j=0; j < pointstmp.Length; j++)
points[i][j] = pointstmp[j];
}
void Update () {
CameraState(); // no problem from here
}
void OnGUI () {
// on user demand using currentFile
GenerateNewSurface();
}
void GenerateNewSurface() { // generate new isosurface using current GUI settings
DestroySurface (); // destroy previous one using Destroy();
MCInstance.MCRecMain(NX[(int)currentFile-1], NY[(int)currentFile-1], NZ[(int)currentFile-1], points[(int)currentFile-1]);
}
}
Files Import
using UnityEngine;
using System;
using System.IO;
class ImportSPIDER {
public void Import (string filePath, out int NZ, out int NY, out int NX, out Vector4[] points) {
TextAsset file = Resources.Load(filePath) as TextAsset;
Stream stream = new MemoryStream(file.bytes);
BinaryReader br = new BinaryReader(stream);
int streamLength = (int)br.BaseStream.Length;
byte[] bytes = new byte[streamLength];
br.Close();
stream.Close ();
// then use the array "bytes" to have my data
}
}
Marching Cubes
using UnityEngine;
using System.Collections.Generic;
using System;
public class MarchingCubesRec {
private GenerateMesh GMInstance;
private int nbIndTriangles; // size of mesh.triangles
private int nbIndVertices; // size of mesh.vertices
private bool[] marchedCubes; // list of length "dataLength", indicates if cubes already have been marched
private int[] vertIndexGrid; // list of length "dataLength", records indexes of previously created vertices, so we can build the triangle indexes list using unique vertices
private int nbMarchedCubes; // nb of cubes that have been marched
private int dataLength; // total nb of points, = ncellsX * ncellsY * ncellsZ
private int[] indList; // stores all grid points indices ("ind") which are >= density threshold ("minValue")
private int indFound;
// + other variables as private
public void MCRecMain(int NX, int NY, int NZ, float minV, Vector4[] P, float tolV) {
ncellsX = NX;
ncellsY = NY;
ncellsZ = NZ;
toleranceValue = tolV;
minValue = minV;
GMInstance = new GenerateMesh();
depthSize = ncellsY;
sliceSize = (ncellsX) * depthSize;
dataLength = ncellsX * ncellsY * ncellsZ;
marchedCubes = new bool[dataLength]; // initialize to false, no cubes have been marched
// + other variables declaration, **using mainly "new"**
MCFindEachIndex (); // find all points inside the surface, > density threshold
for (int i=0; i < indFound; i++) // proceed recursive marching cubes on yet unproceeded cubes
if (!marchedCubes[indList[i]])
MCRec(indList[i]); // lots of calculation without any variable declaration/instantiation or whatever
Mtriangles = new int[nbIndTriangles]; // allocate mesh triangles and vertices, final size
Mvertices = new Vector3[nbIndVertices];
for (int i=0; i < nbIndTriangles; i++)
Mtriangles[i] = TMPtriangles[i];
for (int i=0; i < nbIndVertices; i++)
Mvertices[i] = TMPvertices[i];
GMInstance.GM(Mvertices, Mtriangles, center); // generate display, eventually split on several objects < 65k vertices (Unity limit)
}
Mesh Generation
using UnityEngine;
using System.Collections.Generic;
using System;
public class GenerateMesh {
GameObject GOSurface;
Mesh mesh;
public void GM (Vector3[] Mvertices, int[] Mtriangles, Vector3 center) {
GOSurface = new GameObject("MC Surface");
GOSurface.tag = "surfaceOBJ";
mesh = new Mesh();
GOSurface.AddComponent<MeshFilter>();
GOSurface.AddComponent<MeshRenderer>();
GOSurface.GetComponent<MeshFilter>().mesh = mesh;
GOSurface.renderer.material = new Material(Shader.Find("Diffuse"));
GOSurface.transform.localPosition = center;
mesh.Clear();
mesh.vertices = Mvertices;
mesh.triangles = Mtriangles;
mesh.RecalculateNormals();
}
}
This isn't a forum but a Q&A site (don't worry this is a common mistake for new comers). Which means that you should only post answers, not further info. You should edit the original question if you have further information to add.
Since you have big chunks of code to post I'd also recommend posting them somewhere that you can link to.
So you are effectively keeping all of those points in memory. Plus a temporary set too. Plus a bunch of other things as it runs. That base data requirement sounds like 400$$anonymous$$b to me 128x128x128x16x12. Does it appear to be exceeding that continually or growing towards that? (I haven't worked out how big the other stuff gets yet).
Does this code currently compile? I've tried copying it into Visual Studio but had to "complete" a lot of syntax errors like missing variables etc. In the main script you are calling $$anonymous$$CRec$$anonymous$$ain() with 4 arguments but the version you've pasted here takes 6.
I can't offer much help as this code appears incomplete. Otherwise I might have been able to go over it carefully and try and detect if something is wrong.
In the ImportSPIDER class, why are you putting a $$anonymous$$emory stream on a byte[] array, then putting a reader on it, and then reading it again to a byte[] array? Unity already gives it to you as a byte[] array (which you are using). Or did I miss something?
The actual recursive function "$$anonymous$$CRec" is missing ... ;) It would be interesting how many local variables it has...
Depending on the object, keep in $$anonymous$$d that Unity's $$anonymous$$esh class has a vertices limit of 64k.
Also try to avoid destroying and recreating objects all the time. You should create your gameobject with all the necessary components once and then just update the mesh (clear and reassign the vertices and triangles).
Answer by JeanClaude2010 · Jun 26, 2012 at 05:10 PM
Hello :) Thanks, that was exactly what I was searching for !
Using queues allowed me to pass over my stack overflow problem (and that's really nice :D). Since you seem to have gone through the same problems I'm facing right now, I would have just a few more specific questions.
How can you succeed to multithread this algorithm since you need to recuperate vertices positions (if you want to have unique vertices) ?
If you have to draw different surfaces, I mean different parts really separated one from another, but present one the same file, how would it be possible to proceed ?
Your answer
Follow this Question
Related Questions
How to increase the Call Stack Size in Unity? 0 Answers
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
How to track codes from unity console. 0 Answers
Understanding where to declare variables. Stack vs heap. 2 Answers