memoria con span

in C#

Ottimizzare la memoria utilizzando Span<T>

Reading Time: 2 minutes

Quando utlizziamo una collezione di oggetti, spesso dobbiamo dobbiamo creare nuove collezioni contenenti solo una parte degli oggetti originali. Questo tipo di approccio, apparentemente naturale, porta con se lo svantaggio di creare oggetti duplicati in memoria.

In post precedente avevo già trattato l’introduzione di Range e Index all’interno del framework.

Se dobbiamo lavorare con un porzione di oggetti presenti all’interno di una collezione, anzichè procedere con la clonazione, sarebbe ipotizzabile utilizzare operatori che consentono di estrarre porzioni direttamente dall’origine.

Questo tipo di soluzione consentirebbe di ottimizzare la memoria (non allocandola) e soprattuto di migliorare notevolemente le performance.

In C# 8.0 sono stati introdotti due nuovi tipi: Index e Range.

  • Index: è una variabile di tipo a valore ed un modo per definire una posizione
  • Range: è una variabile di tipo valore che utilizza Index per indicare l’inizio e la fine di un range

A partire da C# 7.2 è stato introdotto un nuovo tipo di dato: Span<T>.

Span<T>

Questo tipo di dato consente di accedere in maniera sicura ai dati contenuti all’interno di un’area contigua di memoria. In particolar modo, la memoria può essere l’head, lo stack o addirittura memoria non gestita. Span<T> consente anche di accedere alla finestra dei dati in modalità readonly: ReadOnlySpan<T>.

Uno Span<T> può quindi essere considerato come una finestra di una memoria esistente, indipendemente da dove essa sia stata allocata.

                         | | | | | |X|X|X|X| | | | | |

Nell’esempio precedente ho indicato con una X la zona contigua individuata da Span<T>.

Supponiamo di aver realizzato un metodo che effettua il parsing di una stringa composta ad esempio da nome e cognome, separati da uno spazio. La funzione “old-style” è:

public string OriginalParser(string fullName)
{
    var lastSpaceIndex = fullName.LastIndexOf(" ", StringComparison.Ordinal);
    return lastSpaceIndex == -1 
    ? string.Empty 
    : fullName.Substring(lastSpaceIndex + 1);
}

di fatto un metodo molto semplice che verifica la presenza di uno spazio all’interno della stringa fullName, e nel caso in cui sia presente, effettua il substring a partire dall’indice. Nel caso in cui non sia stata trovato lo spazio viene restituita una stringa vuota.

Proviamo quindi a riscrivere lo stesso metodo utilizzando Span<T>:

public ReadOnlySpan<char> SpanParser(ReadOnlySpan<char> fullName)
{
    int lastSpaceIndex = fullName.LastIndexOf(' ');
    return lastSpaceIndex == -1
       ? ReadOnlySpan<char>.Empty
       : fullName.Slice(lastSpaceIndex + 1);
}

da notare la differenza della variabile fullName in ingresso al metodo, che a questo non è piu’ di tipo string, ma di tipo ReadOnlySpan<char>.

Il framework mette a disposizione alcuni extensions method che consentono, ad esempio, di convertire una stringa in ReadOnlySpan<char>:

 fullName.AsSpan();

Dopo aver ottenuto l’indice della posizione del primo spazio (con lo stesso codice dell’esempi precedente), viene utilizzato il metodo Slice(…) a partire dalla prima cella dopo lo spazio.

A differenza dell’esempio precedente non viene mai allocata una nuova stringa, clonando quella precedente, bensì utilizzando una finestra readonly.