Reactive Extensions: MemoizeAll

This article is also available in english.

Depuis quelques temps, avec la sortie du Rx Framework et de la programmation réactive/interactive, quelques fonctionnalités intéressantes sont ressorties au travers d’un très bon article de Bart De Smet's à propos de System.Interactive et du “lazy-caching”.

Lorsque l’on utilise LINQ, on trouve deux sortes d’opérateurs : Les opérateurs “Lazy” qui prennent les éléments au fur et à mesure et qui les envoient à mesure qu’ils sont sollicitiés (Select, Where, SelectMany, First, …) et les opérateurs que j’appelerais “rendez-vous” pour lesquels la totalité des éléments de l’énumérable doit être énumérée (Count, ToArray/ToList, OrderBy, …) pour qu’ils produisent un résultat.


Opérateurs “Lazy”

Les opérateurs Lazy sont très intéressants car ils permettent justement d’obtenir de bonnes performances lorsque l’on estime ne pas avoir besoin d’énumérer la totalité de l’énumérable. Cela peut-être aussi utile lorsqu’il est très long d’énumérer tous les éléments de l’énumérable, et que l’on veut n’en récupérer que quelques uns des premiers.

Par exemple :

static IEnumerable<int> GetItems()
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(i);
        yield return i + 1;
    }
}

static void Main()
{
   Console.WriteLine(GetItems().First());
   Console.WriteLine(GetItems().First());
}

Va simplement écrire :

0
1
0
1

On ne va donc énumérer que le premier élément de l’énumérateur GetItems().

Ces opérateurs ont cependant un comportement très important à connaitre: chaque fois qu’ils sont énumérés, ils énumèrent à nouveau leur source. Cela peut-être aussi bien un avantage (énumérer plusieurs fois une source qui change) qu’un inconvénient (énumérer plusieurs fois une source couteuse en ressources).

 

Opérateurs “Rendez-vous”

Ces opérateurs quant à eux sont très intéressants parcequ’ils permettent justement d’énumérer la totalité de l’énumérateur, et dans le cas particulier de ToArray, permet d’avoir une version immuable du contenu l’énumérateur. Ils permettent justement d’éviter que les opérateurs lazy n’énumèrent à nouveau leur source si l’on veut itérer plusieurs fois sur leurs éléments.

Si l’on reprend l’exemple précédent, avec ce code :

static void Main()
{
   var items =
GetItems().ToArray();
   Console.WriteLine(items.Count());
   Console.WriteLine(items.Count());
}

On va avoir ce résultat :

0
1
2
3
4
5
5

Puisque Count a besoin de connaître la totalité des éléments de l’énumérable source pour en déterminer le compte.

Ces opérateurs, eux aussi, énumèrent leur source à chaque utilisation.


Le cas de l’énumération multiple

Un exemple concret de problème posé par l’énumération multiple est la création d’un opérateur de partitionnement d’énumérable. Dans l’exemple donné dans cet article, on peut voir que l’énumérable passé en source est utilisé dans deux clauses “where” différentes, ce qui implique une double énumération de l’énumérable source. Stocker la totalité de l’énumérable source par l’intermédiaire d’un ToArray/ToList serait faisable, mais serait une perte de ressources potentielle puisque l’on ne sait pas si l’énumérable de sortie sera énuméré en totalité (si tenté que ce soit possible, puisque dans le cas d’un énumérable infini, ToArray n’est pas applicable).

Il faudrait donc un intermédiaire entre les opérateurs “Lazy” et “Rendez-vous”.

 

EnumerableEx.MemoizeAll

La classe EnumerableEx apporte une extension, MemoizeAll (tirée du concept de Memoization), qui permet d’avoir cette sorte de juste milieu, et va finalement cacher les éléments de l’énumérable source au fur et à mesure qu’ils sont récupérés. Une sorte de ToArray “lazy”.

Si l’on reprend l’exemple de l’article de Mark Needham, on ferait donc ceci :

var evensAndOdds = Enumerable.Range(1, 10).MemoizeAll().Partition(x => x % 2 == 0);

Dans le cas de cet exemple, le MemoizeAll n’a que très peu d’intéret coté performance, puisque l’énumération de Enumerable.Range ne coute que très peu cher. Mais dans un cas où la source de “Partition” serait un énumérable beaucoup plus couteux, comme une query Linq2Sql par exemple, le caching “lazy” peut se révéler intéressant.

Un des commentaire suggère de faire un implémentation à base de GroupBy, mais cet opérateur évalue à nouveau l’énumérable source lorsqu’un groupe est énuméré. MemoizeAll est aussi tout à fait pertinent, même dans le cas d’un GroupBy. Cela dit, cela reste toujours un compromis entre utilisation mémoire et ressources processeur.

En passant, Bart de Smeth discute de la partie élimination des effets de bords lié à l'énumération multiple d'énumerables par l'utilisation de Memoize et MemoizeAll, ce qui n'est pas vraiment un problème dans ce cas, mais qui est un sujet tout aussi intéressant.

.NET 4.5 ?

Dans une note à part, je trouve dommage que les extensions de EnumerableEx ne se soient pas retrouvées dans .NET 4.0... Elles sont fort utiles, et sont finalement assez peu complexes. Elles sont peut-être arrivées trop tard durant le cycle de développement de .NET 4.0. Dans .NET 4.5, peut-être :)

Publié jeudi 4 février 2010 00:15 par jay
Classé sous , ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires


Les 10 derniers blogs postés

- SharePoint : Bug sur la gestion des permissions et la synchronisation Office par Blog Technique de Romelard Fabrice le 07-10-2014, 11:35

- SharePoint 2007 : La gestion des permissions pour les Workflows par Blog Technique de Romelard Fabrice le 07-08-2014, 11:27

- TypeMock: mock everything! par Fathi Bellahcene le 07-07-2014, 17:06

- Coding is like Read par Aurélien GALTIER le 07-01-2014, 15:30

- Mes vidéos autour des nouveautés VS 2013 par Fathi Bellahcene le 06-30-2014, 20:52

- Recherche un passionné .NET par Tkfé le 06-16-2014, 12:22

- [CodePlex] Projet KISS Workflow Foundation lancé par Blog de Jérémy Jeanson le 06-08-2014, 22:25

- Etes-vous yOS compatible ? (3/3) : la feuille de route par Le blog de Patrick [MVP SharePoint] le 06-06-2014, 00:30

- [MSDN] Utiliser l'approche Contract First avec Workflow Foundation 4.5 par Blog de Jérémy Jeanson le 06-05-2014, 21:19

- [ #ESPC14 ] TH10 Moving mountains with SharePoint ! par Le blog de Patrick [MVP SharePoint] le 06-01-2014, 11:30