Tuesday, April 15, 2008 11:06 AM
Improving Performance Through Stack Allocation (.NET Memory Management: Part 2)
Articles in This Series
Part 1 – Basic Housekeeping
Part 2 – Improving Performance Through Stack Allocation
Part 3 – Increasing the Size of your Stack
Part 4 – Choosing the Right Garbage Collector Settings
Part 5 – Changing Your Garbage Collector Settings on the Fly
In C#, when you create managed objects or arrays of value types, they are created on the Heap and you are passed back a reference to the memory in which that allocated object lives. This is normally a very good thing because it allows you to safely do what you need with it and have it be magically garbage collected when there are no longer any strong references. However, this process incurs a lot of overhead both at allocation time as well as during garbage collection.
The alternative to this is stack allocation. Except for a few exceptions in which the compiler is trying to do some fancy tricks or save you from yourself, references (pointers), structs and value types are kept on the stack. This makes sense because if all of these tiny objects had to be heap allocated and then garbage collected it would take a lot of extra overhead. If you are willing to use unsafe code you can leverage the stack to greatly enhance the performance of some types of applications.
When is it done for me?
Value types are always allocated on the stack:
int num = 10;
All unmanaged members of a struct will be kept on the stack.
public int i;public byte y;public double z;
TestStruct1 ts1 = new TestStruct1();
The following example is a managed struct. The struct and integer i will normally be kept on the stack while k will be a reference to an array allocated on the heap.
public int i; public int k;
TestStruct2 ts2 = new TestStruct2();
ts2.k = new int;
The compiler will also sometimes decide to put things on the stack on its own. I did an experiment with TestStruct2 in which I allocated it both an unsafe and normal context. In the unsafe context the array was put on the heap, but in the normal context when I looked into memory the array had actually been allocated on the stack.
In order to have control over stack allocation you need to execute your code in contexts marked as unsafe. You can do this by using the unsafe keyword at the class, method or code block level:
unsafe class Class1
static unsafe void Main(string args)
You will also need to make sure the /unsafe compiler flag is enabled.
It is important to understand that unsafe code can open the door to security and stability problems. Before writing unsafe code for use in a production environment, make sure to read both the Security section of the C# developer’s guide and the section on Unsafe Code.
It is also important to note that libraries or applications with unsafe code can only be used in Full Trust environments. This is particularly an issue if you are writing a web application which will be deployed in an IIS context which you do not have control over.
The stackalloc keyword
C# has the stackalloc keyword which lets you force unmanaged type arrays to be allocated on the stack inside unsafe contexts:
int* ptr = stackalloc int;
This will allocate a contiguous array of 1024 integers on the stack. Similarly, you can allocate an array of unmanaged structs on the stack as well:
TestStruct1* ptr2 = stackalloc TestStruct1;
This type of allocation is really fast, the garbage collector is never invoked and transversing the array has greatly reduced overhead. This is can be a huge win for performance minded applications. There is one big disadvantage that should be kept in mind however: because things allocated on the stack will go away when they go out of context, they must be copied if you want to use their contents outside of the current method.
The only case in which memory is bounds checked in unsafe code is with stackalloced memory. This constant checking does not come for free and so you may see even better performance when using the fixed keyword inside a stack allocated struct.
The fixed Keyword
.NET 2.0 and later have a keyword called fixed for defining fixed length arrays as part of a struct in unsafe contexts. Unlike using dynamic array allocation, a fixed length array will be considered as part of the struct instead of just a reference. It also has the added bonus of being an unmanaged type and so a struct which uses it will be stack allocated by default.
public int i; public fixed int k;
TestStructFixed tsf = new TestStructFixed();
It is very important to note that arrays defined with the fixed keyword will do no bounds checking for you.
Overview of Stack Allocation
- Fast Allocation
- Fast Array Traversal
- No Need for Garbage Collection
- Only Basic Types and Pointers Can Be Stack Allocated
- Contents Must be Copied to Be Used Out of the Current Scope
- Stack Memory is, By Default, Very Limited in Size ( One Megabyte )
- Unsafe Code Opens the Potential for Security and Stability Problems
- Used For Dynamic Array Allocation
- Can Allocate Arrays of Any Unmanaged Type
- Bounds Checked
- Used For Static Sized Arrays Inside Structs
- Only Available in .NET 2.0 and Newer
- Not Bounds Checked
In this post I did not fully explore all of the differences between the stack and the heap. To explore these differences further, C# Corner has a set of fairly comprehensive set of articles on the topic.