Introduction
La gestion de la mémoire reste un défi critique en 2026 pour les applications C# haute performance. Avec .NET 8, Microsoft a renforcé les outils comme Span
Prérequis
- .NET 8 SDK installé
- Connaissances avancées de C# (async/await, generics)
- Visual Studio 2022 ou Rider
- Notions de base sur le Garbage Collector
Créer le projet benchmark
dotnet new console -n MemoryPerf
cd MemoryPerf
dotnet add package BenchmarkDotNetInitialisation d'un projet console avec BenchmarkDotNet pour mesurer précisément l'impact des optimisations mémoire.
Implémentation Span<T> basique
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;
}
}Ce benchmark compare l'accès via tableau classique et Span
Utilisation de Memory<T> pour async
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
Stackalloc et ref struct
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 permet d'allouer sur la pile. Combiné à ref struct, il garantit une allocation zéro sur le tas pour les buffers temporaires.
Optimisation avec ArrayPool
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 réduit les allocations en recyclant les tableaux. Toujours retourner le buffer dans un finally pour éviter les fuites.
Mesure finale avec Benchmark
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);
}
}
}Benchmark final combinant ArrayPool et Span pour valider la réduction drastique des allocations et du temps GC.
Bonnes pratiques
- Préférez toujours Span
pour les traitements synchrones sur des buffers - Utilisez ArrayPool pour les buffers réutilisables de taille variable
- Évitez de capturer des Span
dans des closures ou des tâches async - Mesurez systématiquement avec BenchmarkDotNet avant optimisation
- Documentez les hypothèses de taille maximale pour les stackalloc
Erreurs courantes à éviter
- Utiliser Span
dans des méthodes async (compilation error) - Oublier de retourner les buffers ArrayPool (fuite mémoire)
- Stackalloc de tailles trop importantes (stack overflow)
- Ignorer les conditions de course sur les buffers partagés
Pour aller plus loin
Approfondissez ces techniques avec nos formations expertes C# .NET. Découvrez également les nouveautés .NET 9 sur la gestion mémoire et les unsafe contexts.