Heap, Stack, Boxing and Unboxing, Performance ... let's order things!

1/24/2022
7 minute read

Okay so there are a lot of terms flying around and we need to order them in order to understand the concepts completely. So lets start with the Heap and Stack. We will see how these two structures impact boxing and unboxing and furthermore why this is expensive. So grab yourself a tea or coffee and begin our journey.

Heap and Stack - Use case

Now one thing to start with: Neither heap nor stack are concepts of C#. Microsoft C# compiler emits instructions which define whether your stuff goes on the heap or stack but you could write your own compiler which takes C# code and does not have any two different types of memory storage.

We are all familiar with a Stack - "Last In First Out". See the last element we put on top of the stack is the first one we can take. That same behavior we have with our memory stack. Let us have a look at the following code snippet:

public void AMethod()
{
    int i = 2; // Line 1
    int j = 3; // Line 2
    MyClass myClass = new MyClass(); // Line 3
}

Now let's go through: On line 1 we have the following stack of memory:

 -------
| Stack |
|-------|
| i = 2 |
 -------

Now lets go to line 2:

 -------
| Stack |
|-------|
| j = 3 |
|-------|
| i = 2 |
 -------

We see that on top of the stack we put the latest variable (j = 3). Now it is getting interesting, line 3 is a class which gets created. Now we have to introduce the second structure: Heap.

 -------------                -------------
|    Stack    |              |     Heap    |
|-------------| references   |-------------|
| myClass(ref)| --------->   | myClass obj |
|-------------|               -------------
|    j = 3    |
|-------------|
|    i = 2    |
 -------------

Ahh okay. So now we encountered a class, so a reference type. And we see that this one gets allocated on the heap and we have a reference on the stack. Now lets go out of the method and let's have a look what happens then and embrace the "magic".

 -------     -------------
| Stack |   |     Heap    |
|-------|   |-------------|
            | myClass obj |
             -------------

So now that we exited our function the Stack is completely empty but our Heap does still exist! Hello Mr. Garbage Collector! Yes that is the sole reason we have a garbage collector. At some point your Heap is full of stuff you don't need anymore and someone has to take care of.

So the Stack is local to the function (and furthermore also to the current thread) but the Heap is not. The stack is so to speak a very convenient way to model a lifetime of those variables. Now why don't we just save everything on one data-structure? Put it simply: massive performance gains. Let me explain.

Primitive data types (like int or double) are not complex. They do not consist out of references to other objects where as most reference types are. If the requirement is of dynamic memory, it’s allocated on the heap or else it goes on a stack. Stay with me for a second and you see why my initial quote from the introduction doesn't make sense and is just not true.

Before we go further: Why is the stack so much faster than the heap?

For that we can check what happens when you allocate something on the heap. If you familiar with C/C++ you know malloc, alloc and friends. They do the heavy work. Well the underlying OS does it technically. Anyway your OS has to check if enough space is available for your object to reserve. If so, you have to reserve that (please keep in mind that we also have things like ASLR which make it even more expensive). Now the block of memory is reserved. Puhhhh kind of done (yes this is simplified).

Now what happens with the stack? Increment the stack pointer. Yes that is all. As shown in my example. Why? Because your stack is allocated when you start your application and stays the whole time. That is why you get StackOverflowExceptions because the thing you try to put on does not fit anymore. Either because you have a recursion which goes to deep (maybe your abort-condition never triggers 😉 ) or for example you have a large array as local variable (yes they can be on the stack, more later).

Now that was a lot to digest. Small recap: Heap and Stack are structures to save some kind of state. The heap is used for dynamic memory and the stack is used for static memory.

So the question arises: Are all value types considered static memory and all reference types considered dynamic memory? Or in other words:

Value types get stored on the stack. Reference types on the heap

Short answer: No!

Long Answer: All reference types are going to the heap. That part is true. But not all value types are stored always on the stack. And I can give you a very simple example.

int i = 0;
int y = 2;
y = i;
y = 3;
Console.Write(i);

I guess it is obvious that the Console.Write(i) part will print just "0" and not "3". But lets take the following example

public class BetterInt
{
    public int Value;
}

BetterInt i = new BetterInt();
i.Value = 0;
BetterInt y = new BetterInt();
y = i;
y.Value = 3;
Console.Write(i.MyValue);

But now our Console will print "3" instead of "0" even though we still have the same value type. Well our value type gets stored on the heap in this case. It has to because the owning component is also on the heap and we just can't remove the property because the owning component still lives. To put it simple: If the owning scope of your value type is on the heap, your value type is on the heap as well.

Another example would be delegates. Delegates can "out-live" their scope where they are called in. Just imagine you have something like that:

public void Init()
{
    int number = 3;
    someClass.OnSent += () => Console.Write(number); // This would print 3
}

We can't just remove the value type even though it is used in the closure.

Boxing and Unboxing

Now we can come to the point where all this is related to boxing and unboxing.

Have a look at this code:

public void BeatBox()
{
    int i = 10; // Line 1
    object o = i; // Line 2
    int io = (int)o; // Line 3
}

Now object o = i this is boxing. It always happens implicit and this: int io = (int)o; is unboxing which always happens explicit. Have a look at our Stack and Heap. First how does it look like on Line 2

 ---------               -------- 
|  Stack  |             |  Heap  |
|---------| references  |--------|
| O(ref)  | -------->   |   10   |
|---------|              --------
| i = 10  |
 ---------

So our object o has a reference from the stack to the heap. And because of that we have to create this object on the heap. That is why it costs time. Small side note: Every pointer is on the stack. When you pass reference types into your function, this reference aka pointer is saved on the Stack. So boxing is when we move data from the stack to the heap. And unboxing is the other way around. Lets see line 3:

 ---------               -------- 
|  Stack  |             |  Heap  |
|---------|             | -------|
| io = 10 | <--------   |        |
|---------| references  |   10   |
| O(ref)  | -------->   |        |
|---------|              --------
| i = 10  |
 ---------

We can see how the unboxing is the way back from the heap to the stack. Unboxing in general is not as expensive as boxing.

Arrays on the stack

You might learned that arrays get passed around by reference which in most of the cases is true. But I also guess you heard about the stackalloc keyword. If not have a look here. It basically allows you to store an array of lets say integers completely on the stack. Now please don't refactor your code to use everywhere stackalloc instead of normal arrays. There are certain limitations and in almost every case you are better of with using "normal" arrays.

int* bar = stackalloc int [100];

This would reserve an array on the stack with 100 integers. Please again: Do not go through your code now and refactor for a tiny performance improvement and possible multiple new introduced bugs!

Summary

I hope I could give you a good introduction about Stack, Heap and how they are related to boxing/unboxing as well as value types and reference types. We have different types of memory management for different scenarios. The stack is fast but has due to its nature certain limitations which the heap does not.

Resources

Enum.Equals - Performance analysis

Did you know that you can spend 100x times longer depending how you compare your enums?

Of course we are speaking here in terms of nanoseconds but this story is much more then just numbers. We'll also talk about boxing and unboxing. So let's dive right into it.

Yes you can create classes on the stack!

In this article we will create a class, aka a reference type on the stack, and therefore don't use any managed memory!

A story about boxing / unboxing and string interpolation in C#

A story about boxing / unboxing and string interpolation in C#. What has string interpolation to do with boxing / unboxing and what's the impact?

An error has occurred. This application may no longer respond until reloaded. Reload x