How do you reduce memory allocations in performance-critical code?

4 minadvanced.NETallocationsperformancememory

Quick Answer

Cut allocations in hot paths by using `Span<T>`/`Memory<T>` and `stackalloc` for slicing without copies, pooling buffers with `ArrayPool<T>`, preferring structs/`ValueTask` where appropriate, avoiding boxing and unnecessary LINQ in tight loops, and caching or reusing objects. Fewer allocations mean fewer and cheaper GCs. Measure with allocation profilers to target the real offenders rather than guessing.

Detailed Answer

Reducing memory allocations minimizes garbage collection overhead and improves performance in hot paths.

Key Strategies:

  1. Use Span<T> and Memory<T> for array slicing:
// Bad: Creates new array
byte[] subset = sourceArray.Skip(10).Take(20).ToArray();

// Good: No allocation
Span subset = sourceArray.AsSpan(10, 20);
  1. Pool and reuse objects:
// Use ArrayPool for temporary buffers
byte[] buffer = ArrayPool.Shared.Rent(1024);
try
{
    // Use buffer
}
finally
{
    ArrayPool.Shared.Return(buffer);
}
  1. Use struct instead of class for small data:
// Allocated on stack, not heap
public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}
  1. Avoid boxing value types:
// Bad: Boxing occurs
object obj = 42;

// Good: Use generics to avoid boxing
void Process(T value) where T : struct { }
  1. Use stackalloc for small temporary arrays:
// Allocated on stack (< 1KB recommended)
Span numbers = stackalloc int[100];
  1. Reuse StringBuilder instances:
private static readonly ThreadLocal _stringBuilder = 
    new ThreadLocal(() => new StringBuilder());

public string BuildString()
{
    var sb = _stringBuilder.Value;
    sb.Clear();
    // Build string
    return sb.ToString();
}
  1. Use ValueTask<T> instead of Task<T> when result is often synchronous:
public ValueTask GetCachedValueAsync(string key)
{
    if (_cache.TryGetValue(key, out int value))
        return new ValueTask(value); // No allocation
    
    return new ValueTask(LoadFromDatabaseAsync(key));
}