Optimizarea Alocărilor de Memorie în C#: De la Garbage Collection la Zero-Allocation cu Span, Memory și ArrayPool în C# (.NET)

Ghid complet de optimizare a memoriei în .NET: eliminarea presiunii pe Garbage Collection prin tehnici Zero-Allocation, diferența critică dintre Span<T> și Memory<T>, pooling de buffere cu ArrayPool și alocare pe stivă cu stackalloc.

Sintaxă C#
ReadOnlySpan<char> span = text.AsSpan(); | ArrayPool<byte>.Shared.Rent(4096);

În dezvoltarea aplicațiilor web de înaltă frecvență sau a sistemelor de tranzacționare în timp real, cel mai mare inamic al latenței predictibile este Garbage Collector-ul. Fiecare alocare inutilă pe Heap (cum ar fi apelurile string.Substring(), string.Split() sau crearea de vectori temporari byte[]) forțează colectorul să declanșeze cicluri frecvente de curățare în Generația 0 și Generația 1.

Ecosistemul modern .NET pune la dispoziție o suită completă de instrumente pentru scrierea de cod Zero-Allocation, permițând parsarea și manipularea fluxurilor de date la viteza memoriei native.

Span<T> vs. Memory<T>: Când și Ce Folosim?
Caracteristică Span<T> / ReadOnlySpan<T> Memory<T> / ReadOnlyMemory<T>
Natură Tip ref struct (alocat exclusiv pe stivă). struct normal (poate fi stocat pe Heap).
Compatibilitate async/await NU poate fi utilizat peste puncte de suspensie await. DA, poate fi transmis și păstrat în metode asincrone.
Scenariu Optim Bucle rapide de parsare sincronă, algoritmi de criptografie, tăiere de text. Operațiuni I/O asincrone pe socket-uri sau fișiere (Stream.ReadAsync).
Evitarea Alocărilor cu ArrayPool<T>.Shared
using System.Buffers;

public async Task ProceseazaFisierMareAsync(Stream streamIntrare)
{
    // În loc de 'new byte[65536]' la fiecare cerere (care ar polua Gen 0 sau LOH):
    // Împrumutăm un buffer pre-alocat direct din pool-ul comun partajat:
    byte[] buffer = ArrayPool<byte>.Shared.Rent(65536);

    try
    {
        int octetiCititi;
        while ((octetiCititi = await streamIntrare.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0)
        {
            // Procesăm felia de date fără alocare nouă:
            ProceseazaChunk(buffer.AsSpan(0, octetiCititi));
        }
    }
    finally
    {
        // Returnăm obligatoriu buffer-ul în pool pentru a fi reutilizat de alte cereri:
        ArrayPool<byte>.Shared.Return(buffer, clearArray: false);
    }
}

private void ProceseazaChunk(ReadOnlySpan<byte> date)
{
    // Operațiuni de verificare și parsare pe stivă
}

Alocare pe Stivă cu stackalloc

Pentru buffere mici cunoscute (sub 1-2 KB), directiva stackalloc alocă memoria direct pe cadrul de stivă al metodei curente, fiind dealocată automat la ieșirea din scope cu cost zero:

Span<byte> hashBuffer = stackalloc byte[32]; // Alocare pe stivă, zero Heap GC!
GenerareSha256(dateIntrare, hashBuffer);