¿Cuándo debería usar Lazy ?

Encontré este artículo sobre Lazy : La pereza en C # 4.0 – Lazy

¿Cuál es la mejor práctica para tener el mejor rendimiento utilizando objetos Lazy? ¿Puede alguien señalarme un uso práctico en una aplicación real? En otras palabras, ¿cuándo debería usarlo?

Normalmente lo usa cuando quiere crear una instancia de algo la primera vez que se usa. Esto retrasa el costo de creación hasta que sea necesario, en lugar de incurrir siempre en el costo.

Usualmente esto es preferible cuando el objeto puede o no ser usado y el costo de construirlo no es trivial.

Debería intentar evitar el uso de Singletons, pero si alguna vez lo necesita, Lazy hace que la implementación de singleton sencillos y seguros para subprocesos sea fácil:

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

Un gran ejemplo del mundo real de donde la carga lenta es útil es con ORM (Object Relation Mappers) como Entity Framework y NHibernate.

Supongamos que tiene una entidad Cliente que tiene propiedades para Nombre, Número de teléfono y Pedidos. Name y PhoneNumber son cadenas regulares, pero Orders es una propiedad de navegación que devuelve una lista de todos los pedidos que el cliente haya realizado.

A menudo querrá revisar todos los de su cliente y obtener su nombre y número de teléfono para llamarlos. Esta es una tarea muy rápida y sencilla, pero imagínese si cada vez que creara un cliente automáticamente fuera e hiciera una combinación compleja para devolver miles de pedidos. La peor parte es que ni siquiera vas a utilizar los pedidos, ¡así que es un desperdicio de recursos!

Este es el lugar perfecto para la carga lenta porque si la propiedad Order es floja, no irá a buscar el pedido del cliente a menos que realmente lo necesite. Puede enumerar los objetos del Cliente obteniendo solo su Nombre y Número de Teléfono mientras la propiedad de Orden duerme pacientemente, lista para cuando la necesite.

He estado considerando el uso de propiedades Lazy para ayudar a mejorar el rendimiento de mi propio código (y para aprender un poco más sobre él). Vine aquí buscando respuestas sobre cuándo usarlo, pero parece que en todas partes voy con frases como:

Utilice la inicialización diferida para diferir la creación de un objeto grande o de uso intensivo de recursos, o la ejecución de una tarea de uso intensivo de recursos, especialmente cuando dicha creación o ejecución no se produzca durante la vida útil del progtwig.

de MSDN Lazy Class

Me siento un poco confundido porque no estoy seguro de dónde trazar la línea. Por ejemplo, considero la interpolación lineal como un cálculo bastante rápido, pero si no necesito hacerlo, ¿puede la inicialización lenta ayudarme a evitar hacerlo y vale la pena?

Al final, decidí probar mi propia prueba y pensé que compartiría los resultados aquí. Lamentablemente, no soy un experto en este tipo de pruebas, por lo que me complace recibir comentarios que sugieran mejoras.

Descripción

Para mi caso, estaba particularmente interesado en ver si Lazy Properties podría ayudar a mejorar una parte de mi código que hace mucha interpolación (la mayor parte no se usa) y entonces he creado una prueba que comparó 3 enfoques.

Creé una clase de prueba separada con 20 propiedades de prueba (llamémoslas propiedades t) para cada enfoque.

  • GetInterp Class: ejecuta la interpolación lineal cada vez que se obtiene una t-property.
  • InitInterp Class: Inicializa las propiedades t ejecutando la interpolación lineal para cada una en el constructor. El get just devuelve un doble.
  • Clase InitLazy: configura las propiedades t como propiedades Lazy para que la interpolación lineal se ejecute una vez cuando se obtiene la propiedad por primera vez. Los resultados posteriores solo deberían devolver un doble ya calculado.

Los resultados de la prueba se miden en ms y son el promedio de 50 instancias o 20 propiedades. Cada prueba se ejecutó 5 veces.

Resultados de la prueba 1: creación de instancias (promedio de 50 instancias)

 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 

Resultados de la Prueba 2: Primer Get (se obtiene un promedio de 20 propiedades)

 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 

Resultados de la Prueba 3: Segundo Get (se obtiene un promedio de 20 propiedades)

 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 

Observaciones

GetInterp es el más rápido de crear instancias como se esperaba porque no está haciendo nada. InitLazy es más rápido de crear instancias que InitInterp lo que sugiere que la sobrecarga en la configuración de las propiedades perezosas es más rápida que mi cálculo de interpolación lineal. Sin embargo, estoy un poco confundido aquí porque InitInterp debería hacer 20 interpolaciones lineales (para configurar sus propiedades t) pero solo toma 0.09 ms para crear instancias (prueba 1), en comparación con GetInterp que toma 0.28 ms para hacer solo una interpolación lineal la primera vez (prueba 2) y 0.1 ms para hacerlo la segunda vez (prueba 3).

InitLazy casi 2 veces más que GetInterp para obtener una propiedad la primera vez, mientras que InitInterp es el más rápido, ya que InitInterp sus propiedades durante la creación de instancias. (Al menos eso es lo que debería haber hecho, pero ¿por qué su resultado de creación de instancias fue mucho más rápido que una sola interpolación lineal? ¿Cuándo exactamente está haciendo estas interpolaciones?)

Lamentablemente, parece que hay una optimización de código automática en mis pruebas. Debería tomar GetInterp al mismo tiempo para obtener una propiedad la primera vez que la segunda vez, pero se muestra como más de 2 veces más rápido. Parece que esta optimización también está afectando a las otras clases, ya que todas toman aproximadamente la misma cantidad de tiempo para la prueba 3. Sin embargo, tales optimizaciones también pueden tener lugar en mi propio código de producción, que también puede ser una consideración importante.

Conclusiones

Si bien algunos resultados son los esperados, también hay algunos resultados inesperados muy interesantes, probablemente debido a la optimización de los códigos. Incluso para las clases que parecen estar trabajando mucho en el constructor, los resultados de la creación de instancias muestran que aún pueden ser muy rápidos de crear, en comparación con obtener una doble propiedad. Si bien los expertos en este campo pueden comentar e investigar más a fondo, mi sensación personal es que tengo que hacer esta prueba nuevamente, pero en mi código de producción para examinar qué tipo de optimizaciones pueden estar ocurriendo allí también. Sin embargo, estoy esperando que InitInterp sea ​​el camino a seguir.

Solo para señalar el ejemplo publicado por Mathew

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

antes de que naciera Lazy, lo hubiéramos hecho de esta manera:

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

Desde MSDN:

Utilice una instancia de Lazy para diferir la creación de un objeto grande o de uso intensivo de recursos o la ejecución de una tarea de uso intensivo de recursos, especialmente cuando dicha creación o ejecución no se produzca durante la vigencia del progtwig.

Además de la respuesta de James Michael Hare, Lazy proporciona la inicialización segura de su valor. Eche un vistazo a la entrada MSDN de la enumeración LazyThreadSafetyMode que describe varios tipos de modos de seguridad de subprocesos para esta clase.