C# Tutorial Lesson 6: The Heap and The Stack

In Lesson2 and Lesson5 we have discussed value types and reference types. Value types are allocated on the Stack and Reference types are allocated on the heap. Value types are faster to allocate, but they cannot be modified (immutable). Reference types are slower to allocate, but they can be modified later on.

Memory management on the Stack and on the Heap

 

Both Stack and Heap memory is limited by the virtual memory available to the running process. Below we examine .NET memory management on the Stack and on the Heap.

The moment stack space is exhausted, .NET runtime throws StackOverflowExcepton.Memory that can no longer be accessed by the process is called “out of scope”and is automatically recycled. Excluding recursive frames, stack space is allocated at compile time and StackOverflowException is limited to recursive loops.

Heap memory management is a bit complicated. Heap variables are always allocated with a new keyword:

Test t=new Test();

However, value types, which are allocated on the stack, can also be allocated with new. Value types that are members of a reference type are allocated on the heap as a part of the reference type.

.NET runtime creates a special thread (it can be configured to run in a separate process – a useful setting for server side application) that monitors allocations of heap space. Once heap consumption grows by several kilobytes (Kb), garbage collection thread stops the process and starts “cleaning” objects that are no longer used. The “cleaning” thread is called .NET Garbage collector. Garbage collector relies on some euristic rules which sound arbitrary to a non-expert like me. For example, Garbage collector assumes that objects that were recently allocated are liklier to be discarded than older objects. I have read lots of articles on .NET garbage collection, but have not seen an explanation of this rule. I compare this rule to the following false (in my opinion) statement: “People who just got their paycheck are more likely to spend it than those who got their paycheck a while ago”.

Having shown that .NET Garbage collector is pretty arbtitrary, I have to point out that most modern (2006) business applications are not CPU, but rather I/O intensive. It therefore does not much matter how optimal garbage collector is, as long as it correctly deallocates unused memory. For large applications, I would estimate 10% reduction in development (coding) cost due to automatic memory management.

Let’s look at the statistics of objects usage in a .NET application. An object is equally likely to be allocated throught the lifetime of the program. Many .NET objects are composite object – the container object instantiates it’s ingradients.Once object’s ingradients are no longer used, so is the object. This is an illustration of why objects that are more recent (ingredients) tend to be deallocated before the older objects (container).

Performance considerations

 

Let’s look at a method that adds two integers

int add(int x, int y) {
return x+y;
}

When you call this method like this:

int x = 3;//stored on the main stack, needs to be copied into the function body
int y = 5;//stored on the main stack, needs to be copied into the function body
int result = add(x,y);//result is copied from function to main stack

,integers 3 and 5 are copied into the add() stack space and the result is allocated on the add’s stack space and than copied back to the result.

An alternative implementation is

int add()//extra field loading code
{
return this.x+this.y;
}
this.x=3;//stored on a heap
this.y=5;//stored on a heap
int result=add();//result is copied from function to main stack

Even though this method has no arguments, fields x and y are copied from the Test object heap to the program stack before being operated on in add().

A quick look at MSIL (see lesson 3?) shows that the second method has two perform two extra operations to load x and y from the heap. On the other hand, the first method has two extra operations to copy x and y into the function body.

A quick test

Test mtest=new Test();
int result,result2;
int iterations=100000000;
DateTime dt=DateTime.Now;
//int iterations=10000000;
for(int i=0;i<iterations;i++)
result=mtest.Add(3,4);
DateTime dt2=DateTime.Now;
Console.WriteLine((dt2.Ticks-dt.Ticks)/100000.0);
for(int i=0;i<iterations;i++)
result2=mtest.Add();
DateTime dt3=DateTime.Now;
Console.WriteLine((dt3.Ticks-dt2.Ticks)/100000.0);

shows that the first method is 6% faster than the second. It is cheaper to copy integers from the main stack into the function body than to load them from the heap.

This entry was posted in Best Practices, Case. Bookmark the permalink.

1 条 C# Tutorial Lesson 6: The Heap and The Stack 的回复

  1. Janey B. Smith说道:

    Interesting! thanks for the post!

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s