19 septembre 2024

C# 7.2 a introduit la structure System.Span. Elle permet de réduire les allocation de mémoire. Nous allons présenter un exemple concret de sont utilisation et expliquer pourquoi elle est si efficace.

Le code de l’application se trouve https://github.com/Fangorne/NewFeatures

Utilisation de Span<T> à la place String

Notre exemple va se porter sur l’utilisation massive de chaîne de caractères. Nous allons déterminer le nombre de lignes vides dans un fichier. Ici nous prendrons un texte assez long “Le Petit Prince”qui sera stocké dans la variable de chaîne _text.

  1. Nous utilisons une boucle foreach pour parcourir chaque caractère.
  2. Pour chaque caractère, on vérifie si c’est un saut de ligne (‘\n’). Si c’est le cas, on incrémente la variable rowNum de 1.
  3. Nous utilisons Substring() pour créer une nouvelle chaîne contenant une ligne de texte. Si cette ligne est vide, nous augmentons notre compteur. La clé ici est que Substring() créera une chaîne sur le tas. Le ramasse-miettes mettra du temps à détruire ces chaînes.
  4. La méthode continue de parcourir la chaîne de caractères jusqu’à ce que tous les caractères aient été examinés.

[Benchmark]
public void ParseWithString()
{
    var indexPrev = 0;
    var indexCurrent = 0;
    var rowNum = 0;
    foreach (var c in _text)
    {
        if (c == '\n')
        {
            indexCurrent += 1;
            var line = _text.Substring(indexPrev, indexCurrent - indexPrev);
            if (line.Equals("\n"))
                rowNum++;
            indexPrev = indexCurrent;
            continue;
        }

        indexCurrent++;
    }
}

Maintenant, le même processus d’analyse utilisant ReadOnlySpan<> :

Dans cette méthode, nous utilisons Span<char> pour parcourir un texte et compter le nombre de lignes dans ce texte. Voici comment cela fonctionne en détail :

  1. Nous créons un Span<char> à partir de la chaîne de caractères _text à l’aide de la méthode AsSpan(). Cela permet de travailler avec une vue sur les caractères de la chaîne de caractères plutôt que de créer une copie des données.
  2. Nous utilisons une boucle foreach pour parcourir chaque caractère dans la plage de mémoire. Pour chaque caractère, on vérifie si c’est un saut de ligne (‘\n’). Si c’est le cas, on incrémente l’index courant de 1 et on utilise la méthode Slice() pour extraire une plage de mémoire contenant les caractères depuis l’index précédent jusqu’à l’index courant.
  3. Nous utilisons la méthode Equals() pour vérifier si la plage de mémoire extraite contient uniquement un saut de ligne. Si c’est le cas, il faut incrémenter le compteur de lignes de 1.
  4. Enfin, nous mettons à jour l’index précédent avec l’index courant et nous continuons à parcourir les caractères restants jusqu’à ce que la boucle se termine.
[Benchmark]
public void ParseWithSpan()
{
    var spanText = _text.AsSpan();
    var indexPrev = 0;
    var indexCurrent = 0;
    var rowNum = 0;
    foreach (var c in spanText)
    {
        if (c == '\n')
        {
            indexCurrent += 1;
            var slice = spanText.Slice(indexPrev, indexCurrent - indexPrev);
            if (slice.Equals("\n", StringComparison.OrdinalIgnoreCase))
                rowNum++;
            indexPrev = indexCurrent;
            continue;
        }

        indexCurrent++;
    }
}

Voici le résultat

Implémentation de Span<t>

Span est implémenté comme une ref struct qui contient un pointeur vers la mémoire et la longueur de la plage similaire à ci-dessous. Cela signifie qu’un Span en C# sera toujours alloué sur la pile, non sur le tas.

public readonly ref struct Span<T>
{
    private readonly ref T _pointer;
    private readonly int _length;
}

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *