Voir le flux RSS

François DORIN

[Actualité] C# 7.2 : à la découverte de Span<T> et Memory<T>

Noter ce billet
par , 15/04/2018 à 22h40 (2004 Affichages)
Dans le billet du jour, nous allons partir à la découverte de nouvelles fonctionnalités introduites avec C# 7.2 : Span<T> et Memory<T>.

L'objectif de ces deux nouvelles structures est de faciliter la manipulation de données, par exemple sous forme de tableau, et ceci de manière performante. Mais cela ne se limite pas aux tableaux ! Il peut s'agir d'un buffer, d'une string, de mémoire non managée, etc... Il s'agit donc d'une abstraction très intéressante pour la manipulation des données. Le seul prérequis : que les données soient contiguës en mémoire.

Span<T>
L'intérêt majeur de Span<T> est de permettre de manipuler un tableau ou un sous-ensemble de tableau de manière performante, car :
  • la structure est allouée sur la pile (donc allocation et déallocation très rapide) ;
  • aucune copie du tableau initiale n'est réalisée.


Prenons ce petit exemple :
Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 
Span<int> span = array;
Span<int> subSpan = span.Slice(4, 3);

Dans un premier temps, on instancie un tableau. Cela sera la base pour la suite.

Ensuite, on crée un Span<T> à partir de ce tableau. On peut noter que la création est aisée grâce à une conversion implicite.

Il est ensuite facile de créer un sous-tableau. Ici, la variable subSpan est un tableau de 3 éléments débutant à partir de l'élément situé à l'index 4 (soit 5, 6, 7).

L'avantage donc, est que ce nouveau « tableau » n'a pas nécessité d'allocation ! Et il y a même encore plus fort.


Ici, on modifie le second élément de notre sous-tableau, pour l'initialiser à 0. Donc maintenant, le sous-tableau contient les éléments 5, 0, 7.

Mais si on regarde le tableau initial, on constate qu'il est également modifié, preuve qu'il n'y a pas de copie !

Comment ça marche ? En fait, très simplement. Une structure Span<T> contient une référence au tableau initial, à l'index de départ et à la taille du tableau. Ensuite, les méthodes de la structure font le nécessaire pour déterminer l'index dans le tableau initial pour les différents accès.

Et ce n'est pas fini ! Span<T> utilise une nouvelle notion introduite avec C# 7.2 : les références sur les structures. Dit comme cela, cela reste un peu flou. Il faut se souvenir que les structures ne sont modifiables qu'à travers une variable. Il n'est pas possible de modifier directement une structure retournée par une fonction par exemple. Donnons un exemple plus concret, en abandonnant notre tableau d'entiers pour passer sur un tableau de structures :
Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
public struct MyStruct
{
    public int Value;
}
 
...
 
MyStruct[] array = {
    new MyStruct() { Value = 1 },
    new MyStruct() { Value = 2 },
    new MyStruct() { Value = 3 }
};
 
Span<MyStruct> span = array;
List<MyStruct> list = new List<MyStruct>(array);
 
span[1].Value = 42;
list[1].Value = 42; // Ne compile pas

Ici, à partir d'un tableau, j'ai initialisé un Span<T> et une List<T>, afin de bien mettre en évidence l'apport lié à l'usage des références sur les structures. Grâce à cela, il est possible de modifier directement une valeur, comme si c'était une variable. Si nous avions une List<T>, cela ne serait pas possible et nous aurions une erreur à la compilation due au fait que l'indexeur d'une List<T> renvoie une structure, qui ne peut donc pas être modifiée directement. Par contre, Span<T> renvoie non pas une structure, mais une référence à la structure, qui peut donc être directement modifiée !

Mais du coup, cela impose quand même quelques limitations. En effet, afin de garder une gestion de la mémoire performante, une instance de Span<T> ne peut être stockée que sur la pile et non sur le tas. De ce fait, il n'est pas possible d'utiliser un Span<T> en tant qu'attribut d'une classe ou dans une expression lambda par exemple.

Dans la plupart des cas, cela n'est pas un véritable souci, car cette structure permet surtout d'optimiser des algorithmes via une gestion performante des sous-tableaux.

Mais il peut y avoir des cas où cela serait utile, notamment dans le cas de procédure asynchrone. Et c'est là que Memory<T> entre en jeu.

Memory<T>
Comme Span<T>, Memory<T> permet de stocker une référence à un tableau, à un indice de départ et à une longueur.
Par contre, contrairement à Span<T>, il n'est pas possible d'accéder directement à un élément en particulier. Pour cela, il est nécessaire de convertir un Memory<T> en Span<T>, qu'il est ensuite possible d'utiliser classiquement. Et cela tombe bien, car justement, Memory<T> dispose d'une propriété nommée Span permettant de réaliser cette opération de conversion !

ReadOnlySpan<T>
Dans le cas où il n'est pas nécessaire de modifier le tableau initial, mais uniquement d'y accéder, il est possible d'utiliser un ReadOnlySpan<T>. C'est comme Span<T> sauf que les accès en écriture ne sont pas autorisés.

L'avantage, c'est que le ReadOnlySpan<T> est compatible avec la classe String afin d'avoir un ReadOnlySpan<char>. En effet, la classe string étant immutable, un string est non modifiable. C'est pourquoi la conversion d'un string en Span<char> n'est pas possible.

Envoyer le billet « C# 7.2 : à la découverte de Span<T> et Memory<T> » dans le blog Viadeo Envoyer le billet « C# 7.2 : à la découverte de Span<T> et Memory<T> » dans le blog Twitter Envoyer le billet « C# 7.2 : à la découverte de Span<T> et Memory<T> » dans le blog Google Envoyer le billet « C# 7.2 : à la découverte de Span<T> et Memory<T> » dans le blog Facebook Envoyer le billet « C# 7.2 : à la découverte de Span<T> et Memory<T> » dans le blog Digg Envoyer le billet « C# 7.2 : à la découverte de Span<T> et Memory<T> » dans le blog Delicious Envoyer le billet « C# 7.2 : à la découverte de Span<T> et Memory<T> » dans le blog MySpace Envoyer le billet « C# 7.2 : à la découverte de Span<T> et Memory<T> » dans le blog Yahoo

Mis à jour 18/04/2018 à 10h04 par François DORIN

Catégories
DotNET , C#

Commentaires

  1. Avatar de longbeach
    • |
    • permalink
    C'est quoi SIZE<T> ?
  2. Avatar de Pyramidev
    • |
    • permalink
    Remarque historique :
    En C++, on a gsl::span qui est pareil que Span de C#, sauf qu'on n'est pas obligé de l'allouer dans la pile.
    gsl::span vient des C++ Core Guidelines qui ont été co-écrites par Bjarne Stroustrup, le créateur du langage C++ et Herb Sutter, un développeur chez Microsoft.
    Microsoft a implémenté la GSL, dont gsl::span.

    Aujourd'hui, j'apprends que cette fonctionnalité se retrouve dans C#.
    Mis à jour 16/04/2018 à 16h52 par Pyramidev (retrait d'une virgule)
  3. Avatar de richardc
    • |
    • permalink
    Span<T> et Memory<T> sont des types, pas une fonctionnalité du langage non ?
  4. Avatar de François DORIN
    • |
    • permalink
    Citation Envoyé par longbeach
    C'est quoi SIZE<T> ?
    Une grosse erreur de ma part sur le titre. C'est corrigé. Il fallait bien entendu lire Span<T>... Merci.

    @Pyramidev, merci pour la note historique

    Citation Envoyé par richardc
    Span<T> et Memory<T> sont des types, pas une fonctionnalité du langage non ?
    Ce sont des types qui ont été introduits en même temps qu'une mise à jour du langage. De plus, le type Span<T> utilise une fonctionnalité qui n'était pas présente auparavant : les variables de type "référence sur une structure".
  5. Avatar de callo
    • |
    • permalink
    Merci François pour ton travail.
    Je pense qu'il y a une petite coquille . Au lieu de
    , ça devrait être
  6. Avatar de François DORIN
    • |
    • permalink
    @callo Merci. C'est corrigé
  7. Avatar de tomlev
    • |
    • permalink
    Salut François,

    Bon article, merci !

    L'intérêt majeur de Span<T> est de permettre de manipuler un tableau ou un sous-ensemble de tableau
    En fait ça ne se limite pas aux tableaux. Ça permet d'accéder à la mémoire sous n'importe quelle forme : tableau "classique", string, mémoire non managée, tableau sur la pile (stackalloc)... Du coup c'est une abstraction super utile puisqu'elle permet d'uniformiser les différents patterns d'accès à la mémoire.
  8. Avatar de François DORIN
    • |
    • permalink
    Citation Envoyé par tomlev
    Bon article, merci !
    Merci



    Citation Envoyé par tomlev
    En fait ça ne se limite pas aux tableaux. Ça permet d'accéder à la mémoire sous n'importe quelle forme : tableau "classique", string, mémoire non managée, tableau sur la pile (stackalloc)... Du coup c'est une abstraction super utile puisqu'elle permet d'uniformiser les différents patterns d'accès à la mémoire.
    Tout à fait. Le seul requis est que la zone de mémoire accédée soit contiguë.

    J'envisage de faire un article dessus plus poussé (quand j'aurais le temps, c'est toujours la même chose ^^). Mais je vais rajouter une note pour le préciser, tu as raison