Usare Span< T > per leggere e manipolare la memoria in .NET

di Cristian Civera, in .NET,

Con l'introduzione di .NET Core, Microsoft ha messo in discussione l'intero .NET Framework che si è sviluppato negli anni e, grazie ad un nuovo processo di sviluppo, fatto da iterazioni più veloci, il coinvolgimento della community e il confrontarsi con altri prodotti, questo ha portato ad un'estrema ottimizzazione delle architetture e le prestazioni del runtime e delle librerie.

In questo processo si è visto come nel mondo .NET esistono tre modi diversi per allocare la memoria a seconda delle necessità: di stack, nell'heap non gestito, nell'heap gestito. Quest'ultimo è quello che usiamo più abitualmente ed è fatto di oggetti creati con la parola chiave new oppure di array. A seconda del tipo di allocazione, però, dobbiamo scorrere e modificare i byte in modi differenti.

Mediante l'introduzione di un nuovo pacchetto NuGet System.Memory attualmente in prerelease, invece, possiamo ora uniformare l'accesso e ottimizzare l'uso della memoria evitando inutili allocazioni. Installato il pacchetto vengono forniti due tipi ReadOnlySpan<T> e Span<T> rispettivamente per leggere o per leggere e modificare una sequenza. Disponiamo, inoltre, di extension method per agevolarci nelle conversioni, per esempio da una stringa.

// Crea uno span sulla base di una stringa
// cioè di un array di char
ReadOnlySpan<char> stringaSpan = "Questa è una stringa".AsSpan();

La funzione Slice, tra le più interessanti, restituisce un nuovo ReadOnlySpan<T>, una struct di basso impatto, che si riferisce sempre alla medesima area di memoria o ad un suo sottoinsieme, senza quindi creare nuovi array o nuove stringhe.

// Stampa 6
Console.WriteLine(stringaSpan.Length);
// Stampa false
Console.WriteLine(stringaSpan.IsEmpty);

// Converto in un nuovo array
string questaString = new string(questaSpan.ToArray());
Console.WriteLine(questaString);

Partendo da un array o da allocazioni non gestite, possiamo creare uno Span<T> e proseguire quindi a travasi o a manipolazioni.

char[] charArray = "Questa è una stringa".ToCharArray();
// Span dall'array, modifabile
Span<char> stringa2Span = new Span<char>(charArray);

// Errore a runtime, troppo piccolo
"Quella non".AsSpan().CopyTo(stringa2Span.Slice(0, 6));

// Copio in uno spazio sufficiente
"quella".AsSpan().CopyTo(stringa2Span.Slice(0, 6));

Span<byte> bytesSpan = stringa2Span.AsBytes();
// Cambio il primo carattere in "Q" maiuscolo. In unicode sono due byte
bytesSpan[0] = 81;
bytesSpan[1] = 0;

// Converto in un array
char[] charArray2 = stringa2Span.ToArray();
// Stampa false, viene ottenuta una nuova allocazione
Console.WriteLine(Object.ReferenceEquals(charArray, charArray2));

// Stampa "Quella è una stringa" dall'array originale
string questaString2 = new string(charArray);
Console.WriteLine(questaString2);

Nell'esempio viene mostrato come sia anche possibile passare ad interpretazioni di tipo diverso, pur manipolando sempre la stessa area di memoria.

L'uso di questa struttura, quindi, è l'ideale quando dobbiamo analizzare o manipolare sequenze di aree di memoria. E' prevista anche l'introduzione di un Memory<T>, una sorta di factory per Span<T>, utile per interfacciarsi anche con Stream, ma al momento non risulta ancora disponibile. Per maggiori dettagli rimandiamo alla issue dedicata allo sviluppo.
https://github.com/dotnet/coreclr/issues/5851

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi