-1

I have a recursive function that has many reference variables taking significant amount of memory. I noticed that every stack frame takes around 1MB, so when the function is being called recurrently 100 times it consumes 100MB.

Let's see simplified example below. From the main method I call a recursive method. With every stack frame there is created a reference ('someList') to the new object on the heap. The memory increases (ID 2,3 on the picture). Then when the particular stack frames are beiing popped out the memory is released (ID 4 on the picture).

enter image description here

What I want to do is to dispose created objects for a particular stack frame before it is popped out. At the time when new stack frames are being popped in.

class Program
{
    static void Main(string[] args)
    {
        var testClass = new TestHeapCollection();

        Console.WriteLine(testClass.IsSaved(10));
        Console.ReadKey();
    }
}

internal class TestHeapCollection
{

    public bool IsSaved(int a)
    {
        var someList = new List<string>();

        /*               
            processing data with local variables
        */

        return a <= 0 || IsSaved(a - 1);
    }
}
helpME1986
  • 933
  • 3
  • 12
  • 26
  • 1
    I don't think the assumptions in this question are correct. A stack frame is "released" when the method returns, and gets overwritten when the next method is called. The objects referenced from within the stack frame are garbage collected from the heap at an indeterminate time after all variables referencing them have been released or set to null. What symptoms are you experiencing that make you think you have a memory consumption issue? – John Wu Aug 06 '18 at 23:01
  • When you say "recurring" do you perhaps mean recursive? Or are you saying that the function is called repeatedly, but not calling itself. – Mike Zboray Aug 06 '18 at 23:11
  • @JoeSewell - When I write 'dispose variables' I mean to delete objects from the heap that the variables refernece to. – helpME1986 Aug 07 '18 at 08:45
  • @mjwills I updated the description of my question. Is it understandable now? – helpME1986 Aug 07 '18 at 08:47
  • @mjwills it debug mode – helpME1986 Aug 07 '18 at 09:33
  • 1
    The GC acts differently in Debug vs Release mode. There is absolutely no point performance profiling anything in Debug mode. https://stackoverflow.com/questions/37462378/why-c-sharp-garbage-collection-behavior-differs-for-release-and-debug-executable – mjwills Aug 07 '18 at 09:33

2 Answers2

0

Thank you all for your help. As @mjwills has written, changing the mode to the Release one makes a difference. I wanted to share with you what I have observed.

Taking the code from my question we can see that the object referenced by 'someList' it is not used anywhere and CLR seems to understand it and the line

 var someList = new List<string>();

does not create any object. The size of the heap does not change. enter image description here

However, if I modify the code like this...

internal class TestHeapCollection
{

    //stackoverflowQuestion
    public bool IsSaved(int a)
    {
        var someList = new List<int> {a-1};
        /*

        processing data with local variables

        */

        return a <= 0 || IsSaved(someList[0]);
    }
}

..the object is created and the value of the list is passed to the next stack frame that pops in. Later, GC is smart enough to dispose objects that were used in previous stack frame! The snaphots of the heap give following results:

enter image description here

helpME1986
  • 933
  • 3
  • 12
  • 26
  • 1
    Because the last thing you do is recurse, it can use tail recursion to reuse the stack frame. You can achieve this explicitly by using a `while` loop and replacing your argument with the new value. Leaving this implicit is quite fragile, as a small change can prevent the compiler from optimizing it. – George Helyar Aug 07 '18 at 10:38
-1

You can't dispose them early because the caller will need them when the callee returns. That is why the variables are arranged in a stack.

If in your particular case the functions are written in a way where caller doesn't need the variables after the callee returns, the pattern you'd be looking for is called tail call optimization. Unfortunately the c# compiler doesn't do this for you automatically. But you can do it yourself usually, by changing your recursive algorithm into an iterative one, e.g. like this poster, or this one, or this one.

John Wu
  • 50,556
  • 8
  • 44
  • 80