Skip to Content
Course content

174: The stackalloc Keyword

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've likely written a method that needs a small, temporary workspace to hold some data before returning a result. Maybe you're parsing a small string, calculating a checksum, or managing a tiny window of values. The instinct for most of us is to just throw a quick array in there: var buffer = new int[16];. It feels harmless. It's only 16 integers, right?

The Cost of Constant Heap Allocations

public int CalculateLocalSum(int[] data)
{
    // Naive approach: allocating on the heap every single call
    int[] tempBuffer = new int[16]; 
    
    for (int i = 0; i < data.Length && i < 16; i++)
    {
        tempBuffer[i] = data[i] * 2;
    }
    
    return tempBuffer.Sum();
}

The problem isn't the amount of memory; it's where that memory lives. Every time you use the new keyword for an array, you're allocating memory on the managed heap. If this method is part of a "hot path"—say, it's called 100,000 times a second in a game loop or a high-frequency trading app—you are creating 100,000 short-lived objects. Even though these objects are tiny, the Garbage Collector (GC) still has to track them and eventually clean them up. This leads to "GC pressure," where your application might stutter because the GC is working overtime to collect thousands of tiny, identical buffers.

Bypassing the Garbage Collector

This is where stackalloc comes in. It allows you to allocate a block of memory on the stack instead of the heap. Stack memory is automatically reclaimed the moment the method returns, meaning the GC never even knows the memory existed. There's no cleanup phase because the memory is just "popped" off the stack.

public int CalculateLocalSum(int[] data)
{
    // Better approach: allocating on the stack
    Span<int> tempBuffer = stackalloc int[16];
    
    for (int i = 0; i < data.Length && i < 16; i++)
    {
        tempBuffer[i] = data[i] * 2;
    }
    
    int sum = 0;
    foreach (var val in tempBuffer) sum += val;
    return sum;
}

In the past, stackalloc required the unsafe keyword because it returned a raw pointer. But thanks to Span<T>, we can now use it in safe code. I generally prefer this pattern whenever I have a known, small upper bound for a temporary buffer. You get the performance of a raw pointer with the safety of a managed span.

The Danger of the Stack Limit

Now, I have to give you a warning: don't get carried away. The heap is massive, but the stack is tiny (usually only 1MB per thread). If you try to stackalloc a massive array, or if you do it inside a recursive method that calls itself thousands of times, you'll trigger a StackOverflowException. Unlike a OutOfMemoryException, a stack overflow is fatal—you can't catch it in a try-catch block; the process just dies.

As a rule of thumb, if you need more than a few kilobytes, stick to the heap. If you aren't sure how large the buffer needs to be at compile time, you can use a hybrid approach: stackalloc for small sizes and ArrayPool<T> for larger ones. It's a bit more boilerplate, but it's how we write high-performance C# that doesn't crash in production.




📋 Practical Task

Refactoring a High-Frequency Signal Smoothing Buffer

You are working on a digital signal processing module. There is a method called SmoothSignal that is called thousands of times per second. Currently, it allocates a temporary double[] array on every call to store a small window of 8 samples, causing significant GC spikes in the telemetry.

Your Task:

  • Modify the SmoothSignal method to replace the heap-allocated array (new double[8]) with a stack-allocated buffer using stackalloc.
  • Ensure the buffer is wrapped in a Span<double> to keep the code safe.
  • Verify that the logic still correctly sums the window and returns the average.
public class SignalProcessor
{
    public double SmoothSignal(double[] incomingData)
    {
        // TODO: Replace this heap allocation with stackalloc
        double[] window = new double[8];
        
        for (int i = 0; i < 8 && i < incomingData.Length; i++)
        {
            window[i] = incomingData[i];
        }

        double sum = 0;
        foreach (var val in window)
        {
            sum += val;
        }

        return sum / 8;
    }
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.