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

- Créer un périphérique Windows To Go 10 ! par Blog de Jérémy Jeanson le 11-21-2014, 04:54

- RDV à Genève le 12 décembre pour l’évènement “SharePoint–Office 365 : des pratiques pour une meilleure productivité !” par Le blog de Patrick [MVP Office 365] le 11-19-2014, 10:40

- [IIS] Erreurs web personnalisées par Blog de Jérémy Jeanson le 11-19-2014, 00:00

- BDD/TDD + Javascript par Fathi Bellahcene le 11-16-2014, 16:57

- Sécuriser sans stocker de mots de passe par Blog de Jérémy Jeanson le 11-15-2014, 08:58

- Où télécharger la preview de Visual Studio 2015 ? par Blog de Jérémy Jeanson le 11-13-2014, 21:33

- Les cartes sont partout ! par Le blog de Patrick [MVP Office 365] le 11-13-2014, 17:26

- [ #Office365 ] Courrier basse priorité ! par Le blog de Patrick [MVP Office 365] le 11-12-2014, 08:56

- [Oracle] Fichier oranfsodm12.dll absent du package client par Blog de Jérémy Jeanson le 11-10-2014, 20:44

- [ #Office365 ] Le chapitre 1 des Groupes est écrit, et alors ? par Le blog de Patrick [MVP Office 365] le 11-10-2014, 20:23