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?
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.
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;
}
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.
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.
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.
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 ;
}
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.