web-dev-qa-db-fra.com

Quand devrais-je utiliser Lazy <T>?

J'ai trouvé cet article sur Lazy: Lazness in C # 4.0 - Lazy

Quelle est la meilleure pratique pour obtenir les meilleures performances en utilisant des objets Lazy? Quelqu'un peut-il m'indiquer une utilisation pratique dans une application réelle? En d'autres termes, quand devrais-je l'utiliser?

298
danyolgiax

Vous l'utilisez généralement lorsque vous souhaitez instancier quelque chose la première fois qu'il est réellement utilisé. Cela retarde le coût de sa création jusqu'à ce que, le cas échéant, au lieu de toujours en supporter le coût.

Cela est généralement préférable lorsque l'objet peut ou ne peut pas être utilisé et que le coût de sa construction est non négligeable.

218
James Michael Hare

Vous devriez essayer d'éviter d'utiliser Singletons, mais si vous en avez besoin, Lazy<T> facilite la mise en œuvre de singletons paresseux et thread-safe:

public sealed class Singleton
{
    // Because Singleton's constructor is private, we must explicitly
    // give the Lazy<Singleton> a delegate for creating the Singleton.
    static readonly Lazy<Singleton> instanceHolder =
        new Lazy<Singleton>(() => new Singleton());

    Singleton()
    {
        // Explicit private constructor to prevent default public constructor.
        ...
    }

    public static Singleton Instance => instanceHolder.Value;
}
116
Matthew

Un bel exemple dans le monde réel où le chargement paresseux est pratique est celui des ORM (Object Relation Mappers) tels que Entity Framework et NHibernate.

Supposons que vous avez une entité Client qui possède des propriétés pour Nom, Numéro de téléphone et Commandes. Name et PhoneNumber sont des chaînes ordinaires, mais Orders est une propriété de navigation qui renvoie une liste de toutes les commandes passées par le client.

Vous pouvez souvent consulter tous vos clients et obtenir leur nom et leur numéro de téléphone pour les appeler. C’est une tâche très simple et rapide, mais imaginez si chaque fois que vous créez un client, il est automatiquement associé à une jointure complexe pour renvoyer des milliers de commandes. Le pire, c'est que vous n'allez même pas utiliser les commandes, c'est donc un gaspillage total de ressources!

C'est l'endroit idéal pour le chargement paresseux, car si la propriété Order est paresseuse, elle n'ira pas chercher toute la commande du client, sauf si vous en avez réellement besoin. Vous pouvez énumérer les objets Client en obtenant uniquement leur nom et leur numéro de téléphone pendant que la propriété Order dort patiemment, prête lorsque vous en avez besoin.

79
Despertar

J'ai envisagé d'utiliser les propriétés Lazy<T> pour améliorer les performances de mon propre code (et en apprendre un peu plus à ce sujet). Je suis venu ici pour chercher des réponses sur le moment de l'utiliser, mais il semble que partout où je vais, des phrases telles que:

Utilisez l'initialisation différée pour différer la création d'un objet volumineux ou gourmand en ressources, ou l'exécution d'une tâche gourmande en ressources, en particulier lorsqu'une telle création ou exécution risque de ne pas se produire pendant la durée du programme.

à partir de MSDN Lazy <T> Class

Je suis un peu confus parce que je ne sais pas trop où tracer la ligne. Par exemple, je considère l'interpolation linéaire comme un calcul assez rapide, mais si je n'ai pas besoin de le faire, une initialisation lente peut-elle m'aider à éviter de le faire et vaut-elle la peine?

Finalement, j'ai décidé d'essayer mon propre test et j'ai pensé partager les résultats ici. Malheureusement, je ne suis pas vraiment un expert dans ce type de tests et je suis donc heureux de recevoir des commentaires suggérant des améliorations.

Description

Pour mon cas, j'étais particulièrement intéressé de voir si Lazy Properties pouvait aider à améliorer une partie de mon code qui effectue beaucoup d'interpolation (la plupart étant inutilisée). J'ai donc créé un test comparant trois approches.

J'ai créé une classe de test séparée avec 20 propriétés de test (appelons-les t-properties) pour chaque approche.

  • GetInterp Classe: Exécute une interpolation linéaire chaque fois qu'une propriété t est obtenue.
  • InitInterp Classe: Initialise les propriétés t en exécutant l'interpolation linéaire pour chacune d'elles dans le constructeur. Le get retourne juste un double.
  • InitLazy Class: Définit les propriétés t en tant que propriétés paresseuses afin que l'interpolation linéaire soit exécutée une fois lors de l'obtention de la propriété. Les gains suivants doivent simplement renvoyer un double déjà calculé.

Les résultats du test sont mesurés en ms et correspondent à la moyenne de 50 instanciations ou de 20 propriétés obtenues. Chaque test a ensuite été exécuté 5 fois.

Résultats du test 1: Instanciation (moyenne de 50 instanciations)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.005668 0.005722 0.006704 0.006652 0.005572 0.0060636 6.72
InitInterp 0.08481  0.084908 0.099328 0.098626 0.083774 0.0902892 100.00
InitLazy   0.058436 0.05891  0.068046 0.068108 0.060648 0.0628296 69.59

Résultats du test 2: Premier arrivé (moyenne sur 20 biens acquis)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.263    0.268725 0.31373  0.263745 0.279675 0.277775 54.38
InitInterp 0.16316  0.161845 0.18675  0.163535 0.173625 0.169783 33.24
InitLazy   0.46932  0.55299  0.54726  0.47878  0.505635 0.510797 100.00

Résultats du test 3: Second Get (moyenne sur 20 propriétés)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.08184  0.129325 0.112035 0.097575 0.098695 0.103894 85.30
InitInterp 0.102755 0.128865 0.111335 0.10137  0.106045 0.110074 90.37
InitLazy   0.19603  0.105715 0.107975 0.10034  0.098935 0.121799 100.00

Observations

GetInterp est le plus rapide à instancier comme prévu car il ne fait rien. InitLazy est plus rapide à instancier que InitInterp, ce qui suggère que la surcharge liée à la configuration des propriétés paresseuses est plus rapide que mon calcul d'interpolation linéaire. Cependant, je suis un peu confus ici parce que InitInterp devrait faire 20 interpolations linéaires (pour configurer ses propriétés t) mais il ne faut que 0,09 ms pour instancier (test 1), par rapport à GetInterp ce qui prend 0,28 ms pour effectuer une seule interpolation linéaire la première fois (test 2) et 0,1 ms pour la deuxième fois (test 3).

Il faut InitLazy presque deux fois plus longtemps que GetInterp pour obtenir une propriété la première fois, tandis que InitInterp est le plus rapide, car il a renseigné ses propriétés lors de l'instanciation. (Du moins, c'est ce qu'il aurait dû faire, mais pourquoi le résultat de l'instanciation est-il plus rapide qu'une interpolation linéaire? Quand exactement fait-il ces interpolations?)

Malheureusement, il semble qu'il y ait une optimisation automatique du code en cours dans mes tests. GetInterp devrait prendre le même temps pour obtenir une propriété la première fois, mais la valeur affichée est plus de deux fois plus rapide. Il semble que cette optimisation affecte également les autres classes car elles prennent toutes à peu près le même temps pour le test 3. Cependant, de telles optimisations peuvent également avoir lieu dans mon propre code de production, ce qui peut également être une considération importante.

Conclusions

Bien que certains résultats soient conformes aux attentes, il existe également des résultats inattendus très intéressants, probablement dus à des optimisations de code. Même pour les classes qui ont l’air de faire beaucoup de travail dans le constructeur, les résultats d’instanciation montrent qu’elles peuvent toujours être très rapides à créer, par rapport à l’obtention d’une propriété double. Bien que les experts dans ce domaine puissent peut-être commenter et enquêter plus en profondeur, mon sentiment personnel est que je dois refaire ce test, mais sur mon code de production afin d’examiner le type d’optimisation qui pourrait avoir lieu là aussi. Cependant, je m'attends à ce que InitInterp soit le chemin à parcourir.

38
Ben

Juste pour montrer l'exemple posté par Mathew

public sealed class Singleton
{
    // Because Singleton's constructor is private, we must explicitly
    // give the Lazy<Singleton> a delegate for creating the Singleton.
    private static readonly Lazy<Singleton> instanceHolder =
        new Lazy<Singleton>(() => new Singleton());

    private Singleton()
    {
        ...
    }

    public static Singleton Instance
    {
        get { return instanceHolder.Value; }
    }
}

avant la naissance du paresseux, nous l'aurions fait de cette façon:

private static object lockingObject = new object();
public static LazySample InstanceCreation()
{
    if(lazilyInitObject == null)
    {
         lock (lockingObject)
         {
              if(lazilyInitObject == null)
              {
                   lazilyInitObject = new LazySample ();
              }
         }
    }
    return lazilyInitObject ;
}
14

De MSDN:

Utilisez une instance de Lazy pour différer la création d'un objet volumineux ou gourmand en ressources, ou l'exécution d'une tâche gourmande en ressources, en particulier lorsqu'une telle création ou exécution risque de ne pas se produire pendant la durée du programme.

En plus de la réponse de James Michael Hare, Lazy fournit une initialisation sans faille de votre valeur. Jetez un oeil à LazyThreadSafetyMode énumération entrée MSDN décrivant divers types de modes de sécurité de thread pour cette classe.

12
Vasea