Skip to content
Learni
View all tutorials
Développement Backend

How to Optimize Memory Performance in C# .NET 8 in 2026

18 minEXPERT
Lire en français

Introduction

Memory management remains a critical challenge in 2026 for high-performance C# applications. With .NET 8, Microsoft has strengthened tools like Span and Memory that allow avoiding unnecessary allocations. This tutorial guides you step by step to expert mastery of these concepts. You will learn to reduce pressure on the GC and achieve near-native performance. Each technique is illustrated with complete and functional code.

Prerequisites

  • .NET 8 SDK installed
  • Advanced knowledge of C# (async/await, generics)
  • Visual Studio 2022 or Rider
  • Basic understanding of the Garbage Collector

Create the benchmark project

terminal
dotnet new console -n MemoryPerf
cd MemoryPerf
dotnet add package BenchmarkDotNet

Initialize a console project with BenchmarkDotNet to precisely measure the impact of memory optimizations.

Basic Span<T> Implementation

Program.cs
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

public class Program {
    public static void Main() => BenchmarkRunner.Run<MemoryBenchmarks>();
}

[MemoryDiagnoser]
public class MemoryBenchmarks {
    private byte[] data = new byte[10000];

    [Benchmark(Baseline = true)]
    public int SumWithArray() {
        int sum = 0;
        for (int i = 0; i < data.Length; i++) sum += data[i];
        return sum;
    }

    [Benchmark]
    public int SumWithSpan() {
        var span = new Span<byte>(data);
        int sum = 0;
        for (int i = 0; i < span.Length; i++) sum += span[i];
        return sum;
    }
}

This benchmark compares access via a classic array and Span. Span avoids additional allocations and enables views without copying.

Using Memory<T> for async

AsyncMemory.cs
using System;
using System.Threading.Tasks;

public class AsyncMemoryExample {
    public async Task<int> ProcessAsync(Memory<byte> buffer) {
        await Task.Delay(10);
        int sum = 0;
        foreach (var b in buffer.Span) sum += b;
        return sum;
    }

    public async Task Run() {
        byte[] data = new byte[5000];
        var result = await ProcessAsync(data);
        Console.WriteLine(result);
    }
}

Memory can be used in async methods unlike Span. It allows passing buffers without allocation while remaining compatible with asynchrony.

Stackalloc and ref struct

StackAllocDemo.cs
using System;

public ref struct BufferWrapper {
    public Span<byte> Data { get; }
    public BufferWrapper(Span<byte> data) => Data = data;
}

public class Demo {
    public unsafe void Process() {
        Span<byte> stackBuffer = stackalloc byte[256];
        var wrapper = new BufferWrapper(stackBuffer);
        wrapper.Data[0] = 42;
        Console.WriteLine(wrapper.Data[0]);
    }
}

stackalloc allows allocation on the stack. Combined with ref struct, it guarantees zero heap allocation for temporary buffers.

Optimization with ArrayPool

ArrayPoolDemo.cs
using System;
using System.Buffers;

public class PoolExample {
    public void RentAndReturn() {
        var pool = ArrayPool<byte>.Shared;
        byte[] buffer = pool.Rent(4096);
        try {
            buffer[0] = 1;
            // traitement
        } finally {
            pool.Return(buffer);
        }
    }
}

ArrayPool reduces allocations by recycling arrays. Always return the buffer in a finally block to avoid leaks.

Final Benchmark Measurement

FinalBench.cs
using BenchmarkDotNet.Attributes;

[MemoryDiagnoser]
public class FinalBench {
    [Benchmark]
    public void OptimizedPath() {
        var pool = ArrayPool<byte>.Shared;
        var buffer = pool.Rent(8192);
        try {
            var span = buffer.AsSpan(0, 8192);
            span.Fill(0xFF);
        } finally {
            pool.Return(buffer);
        }
    }
}

Final benchmark combining ArrayPool and Span to validate the drastic reduction in allocations and GC time.

Best Practices

  • Always prefer Span for synchronous processing on buffers
  • Use ArrayPool for reusable buffers of variable size
  • Avoid capturing Span in closures or async tasks
  • Always measure with BenchmarkDotNet before optimizing
  • Document maximum size assumptions for stackalloc

Common Mistakes to Avoid

  • Using Span in async methods (compilation error)
  • Forgetting to return ArrayPool buffers (memory leak)
  • Stackalloc of excessively large sizes (stack overflow)
  • Ignoring race conditions on shared buffers

Further Reading

Deepen these techniques with our expert C# .NET training. Also discover the new features in .NET 9 for memory management and unsafe contexts.