In this lesson, you will learn how to leverage structs to control memory layout and minimize Garbage Collector (GC) pressure by choosing between stack and heap allocation.
Stack vs. Heap Allocation
In C#, class types are reference types, meaning they are always allocated on the heap, which necessitates GC tracking and eventual cleanup. Conversely, struct types are value types. When declared as a local variable within a method, a struct lives on the stack. Stack allocation is nearly instantaneous and effectively "disappears" when the method scope ends, placing zero pressure on the GC.
Controlling the Layout
By default, the .NET runtime manages the field layout of your types to optimize for performance. However, when you use a struct, you gain finer control over how data is laid out in memory. Using the [StructLayout] attribute, you can dictate exactly how bytes are arranged:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PacketHeader
{
public byte Type;
public int Id;
}
In the example above, LayoutKind.Sequential ensures the fields are laid out in the order they appear, and Pack = 1 removes padding that the compiler might otherwise insert for alignment. This is critical when you need to match memory layouts for interop or optimize the cache locality of large arrays of data.
When to Use Structs for Performance
The goal of using struct here is to reduce the number of objects the GC must track. If you have a collection of 10,000 objects, using a List<MyClass> creates 10,000 individual heap objects that the GC must traverse during mark-and-sweep cycles. Using a MyStruct[] allocates one contiguous block of memory on the heap, where the data itself is stored inline. The GC sees one array object, not 10,000 independent objects, drastically reducing the "objects per collection" overhead.
However, keep in mind that structs are copied by value. Avoid passing large structs by value, as the cost of copying memory can outweigh the benefits of avoiding heap allocation. Use in or ref modifiers when passing large structs to methods to maintain performance.
Key Takeaway: Use structs to keep data on the stack or inline within arrays, effectively reducing GC pressure by treating your data as a contiguous block of memory rather than individual heap-allocated objects.