Back to Blog
C#

Using C# stackalloc for High-Performance Buffers

Learn how to use c# stackalloc to allocate buffers on the stack, avoid heap allocations, and improve performance in high-frequency code paths.

C#stackallocSpan<T>memory managementperformanceunsafe code
Diagram of a stack frame with a stackalloc buffer shown in green, highlighting the brief lifetime of the allocated memory.

When a method needs a temporary buffer, the usual choice is to allocate an array with new. But that array lives on the managed heap, and allocating it costs time and memory. In performance-sensitive code, c# stackalloc offers an alternative: a buffer allocated on the call stack. This article explains how stackalloc works, where it helps, and the constraints that keep it from being a universal replacement for arrays.

What stackalloc Actually Allocates

stackalloc allocates a block of memory on the current stack frame. The memory is automatically reclaimed when the method returns, so no garbage collection is involved. The syntax is straightforward:

Span<byte> buffer = stackalloc byte[256];

The variable buffer is a Span<byte> that points to the stack-allocated memory. You can use it like an array: index into it, slice it, and pass it to methods that accept Span<T>. The stack allocation is fast because it only adjusts the stack pointer, and it has no allocation overhead on the managed heap.

The requirement is that stackalloc must be used in an unsafe context. However, when you assign the result to a Span<T>, the C# compiler allows it without the unsafe keyword, as shown above. This is the recommended way to use stackalloc because it avoids raw pointers and keeps the code verifiable.

Declaring stackalloc Buffers with Span<T>

Using Span<T> with stackalloc is the most practical pattern. Here is an example that processes a set of integers without allocating an array:

public static int SumEvenNumbers(ReadOnlySpan<int> numbers) { Span<int> buffer = stackalloc int[numbers.Length]; int count = 0; foreach (var n in numbers) { if (n % 2 == 0) { buffer[count++] = n; } } int sum = 0; for (int i = 0; i < count; i++) { sum += buffer[i]; } return sum; }

This code copies even numbers into a stack-allocated buffer and then sums them. The buffer is created with stackalloc int[numbers.Length], which may be zero-length if numbers is empty. Zero-length buffer is valid, and the resulting Span is empty.

Because Span<T> is a ref struct, it cannot be used as a field in a class or captured in a lambda. That limitation confines stackalloc usage to a single method and its called routines, which is exactly the intended scope.

stackalloc in Unsafe Code with Pointers

Before Span<T> existed, stackalloc was typically used directly with pointers inside an unsafe block. That pattern is still available and is sometimes necessary for interop or for maximizing performance in very low-level code.

unsafe { int* buffer = stackalloc int[128]; for (int i = 0; i < 128; i++) { buffer[i] = i * i; } }

The pointer buffer points to the stack-allocated memory. You must be careful with pointer arithmetic and bounds, as the compiler cannot catch overruns. Because the pointer is not managed by the runtime, the developer is responsible for correctness. This approach should be limited to code that genuinely requires pointer access, such as calling certain native APIs.

Conditional Stack Allocation to Avoid Large Buffers

The stack has limited space. Allocating a large buffer from a method can blow the stack quickly, resulting in a StackOverflowException that cannot be caught. The typical guidance is to use stackalloc for small buffers only. A common approach is to allocate from the stack when the data fits a threshold, and fall back to a heap array otherwise.

public static void Process(int count) { const int MaxStackBytes = 1024; int byteCount = count * sizeof(int); if (byteCount <= MaxStackBytes) { Span<int> buffer = stackalloc int[count]; // Use stack buffer. } else { int[] buffer = new int[count]; // Use heap array. } }

This pattern gives you the performance benefit of stack allocation for common small cases while avoiding the risk of stack overflow for large inputs. The threshold should be chosen based on your environment and the depth of the call stack. A limit of 1 KB is conservative, but you may adjust it after profiling.

Stackalloc with Array Initializers

C# supports initializing stackalloc memory with an array initializer. This feature was introduced to allow simple and safe assignments.

Span<int> digits = stackalloc int[] { 1, 2, 3, 4, 5 };

This is equivalent to declaring a stack array and copying the values in. It is useful when you need a constant set of data that should not allocate on the heap, such as lookup tables inside a method. The syntax is clear, and the compiler generates the appropriate code to copy the values into the stack buffer.

Note that the array initializer with stackalloc is only allowed when the target type is Span<T>. You cannot use it with pointers directly. This is a small limitation but fine for most scenarios.

Comparing stackalloc with Heap Allocation

The primary benefit of stackalloc is avoiding heap allocation and garbage collection. In a loop that executes millions of times, allocating an array each iteration would put pressure on the GC and slow down execution. With stackalloc, the memory is reused from the stack and freed instantly when the method returns.

Aspectstackallocnew T[]
Memory locationstackheap
Allocation costvery lowmoderate
GC pressurenoneadds garbage
Size limitlimited by stackmemory available
Lifetimetill method returnuntil GC collects
Use outside methodnot possiblepossible

This comparison shows that stackalloc is ideal for temporary, small buffers within a single method. If the data must live beyond the method call, or if the buffer size is unpredictable and potentially large, a heap array is required.

Runtime Behavior and Safety Considerations

Because stackalloc lives on the stack, it is tied to the current call stack. The memory is not subject to garbage collection, and the pointer becomes invalid once the method returns. If you try to use a pointer after the method returns, you get undefined behavior. With Span<T>, the compiler prevents this because a Span cannot be returned from the method in a way that outlives the stack frame (it is a ref struct). However, you can still pass it down to methods that execute synchronously.

Stack space is finite. The default stack size for a thread is typically around 1 MB on Windows, but it can be less. Deep recursion combined with large stackalloc calls can cause a stack overflow. Since a StackOverflowException is not catchable, it is critical to limit the amount of stackalloc used in each frame.

The unsafe pointer variant has additional safety hazards. Writing beyond the allocated area corrupts memory without immediate crash, leading to subtle bugs. Always keep manual pointer usage to a minimum, and prefer Span<T> for safety.

Stackalloc in Synchronous and Async Code

Span<T> and stackalloc are not allowed in async methods because the method may yield control and continue on a different thread. The stack frame may not be preserved. Similarly, iterators that use yield return cannot contain stackalloc. If you need a buffer in an async context, you have to use a regular array or a pooled buffer from ArrayPool<T>.

In synchronous code, stackalloc works well. Since the stack frame is guaranteed to stay active, the buffer remains valid. This restriction is important when designing APIs that might be used in async paths.

Choosing When to Use stackalloc

The decision to use stackalloc comes down to several factors. Use it when you need a small temporary buffer in a method that is called frequently. The buffer size should be small enough to avoid stack pressure. If the buffer is larger than a few kilobytes, strongly consider ArrayPool<T> instead, which reuses larger heap buffers and avoids allocation.

stackalloc also shines in algorithms that construct intermediate data, like formatting, parsing, or encoding. For example, converting a number to a string representation is often done in a stack-allocated buffer. The System.Text namespace uses stackalloc internally for small values.

Here is a practical example that formats a date to a fast custom format using stackalloc:

public static string FormatDate(int year, int month, int day) { Span<char> buffer = stackalloc char[10]; buffer[0] = (char)('0' + year / 1000 % 10); buffer[1] = (char)('0' + year / 100 % 10); buffer[2] = (char)('0' + year / 10 % 10); buffer[3] = (char)('0' + year % 10); buffer[4] = '-'; buffer[5] = (char)('0' + month / 10); buffer[6] = (char)('0' + month % 10); buffer[7] = '-'; buffer[8] = (char)('0' + day / 10); buffer[9] = (char)('0' + day % 10); return new string(buffer); }

This method uses a stackalloc buffer for the characters of the formatted date, avoiding any heap allocation except for the final string. This is a legitimate use pattern when you need high-performance formatting in loops.

Stackalloc is not a silver bullet. It adds complexity and requires careful reasoning. For a small buffer in a seldom-called method, the performance difference is negligible. But in high-frequency paths, the savings from avoiding GC pressure and allocation can be substantial.

Ultimately, the decision hinges on the size and lifetime of the buffer. If it fits on the stack, is short-lived, and the method is called often, stackalloc is a strong candidate. Otherwise, stick with arrays or pooled buffers.

c# stackalloc: Practical Usage and Code Examples | RYUSLOG DEV