remarks cref c# .net lazy-evaluation

c# - cref - ¿Cuándo debo usar Lazy<T>?



remarks c# (6)

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

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

Encontré este artículo sobre Lazy : Laziness en C # 4.0 - Lazy

¿Cuál es la mejor práctica para tener el mejor rendimiento al usar objetos perezosos? ¿Alguien puede indicarme un uso práctico en una aplicación real? En otras palabras, ¿cuándo debo usarlo?


Desde MSDN:

Use una instancia de Lazy para diferir la creación de un objeto grande o que requiera un uso intensivo de recursos o la ejecución de una tarea que requiera un uso intensivo de recursos, especialmente cuando dicha creación o ejecución no se produzca durante la vida útil del programa.

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


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

Use la inicialización perezosa 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 programa.

de MSDN Lazy <T> Class

Me quedo un poco confundido porque no estoy seguro de dónde dibujar la línea. Por ejemplo, considero la interpolación lineal como un cálculo bastante rápido, pero si no necesito hacerlo, entonces la inicialización perezosa me ayuda a evitar hacerlo y ¿vale la pena?

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

Descripción

En mi caso, me interesó particularmente 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, por lo tanto, 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.

  • Clase GetInterp: ejecuta la interpolación lineal cada vez que se obtiene una propiedad t.
  • Clase InitInterp: Inicializa las propiedades t ejecutando la interpolación lineal para cada una en el constructor. El get solo devuelve un doble.
  • Clase InitLazy: configura las propiedades t como propiedades perezosas para que la interpolación lineal se ejecute una vez cuando se obtiene la propiedad por primera vez. Las siguientes tomas deberían devolver un doble ya calculado.

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

Resultados de la prueba 1: instanciación (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: primera obtención (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: Segunda obtención (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 espera porque no hace nada. InitLazy es más rápido de crear instancias que InitInterp lo que sugiere que la sobrecarga en la configuración de 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 estar haciendo 20 interpolaciones lineales (para configurar sus propiedades t), pero solo está tomando 0.09 ms para crear una instancia (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).

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

Lamentablemente, parece que hay algunas optimizaciones automáticas de código en mis pruebas. Debería tomar GetInterp el 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 están tomando aproximadamente la misma cantidad de tiempo para la prueba 3. Sin embargo, estas optimizaciones también pueden tener lugar en mi propio código de producción, lo 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 las optimizaciones del código. Incluso para las clases que parecen estar haciendo mucho trabajo 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 propiedad doble. Si bien los expertos en este campo pueden comentar e investigar más a fondo, mi opinión personal es que necesito volver a realizar esta prueba pero en mi código de producción para examinar qué tipo de optimizaciones pueden tener lugar allí también. Sin embargo, estoy esperando que InitInterp sea ​​el camino a seguir.


Normalmente lo usas cuando quieres crear una instancia de algo la primera vez que se usa realmente. Esto retrasa el costo de crearlo hasta si / cuando se necesita, en lugar de incurrir siempre en el costo.

Por lo general, esto es preferible cuando el objeto puede o no ser usado y el costo de su construcción no es trivial.


Solo para señalar el ejemplo publicado por Mateo

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

antes de que naciera el perezoso lo habríamos 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 ; }


Un gran ejemplo del mundo real en el que la carga perezosa es útil es con ORM (Object Relation Mappers), como Entity Framework y NHibernate.

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

A menudo, es posible que desee consultar a todos sus clientes y obtener su nombre y número de teléfono para llamarlos. Esta es una tarea muy rápida y sencilla, pero imagine que cada vez que creara un cliente, automáticamente iba e hizo una combinación compleja para devolver miles de pedidos. ¡Lo peor es que ni siquiera vas a utilizar los pedidos, por lo que es una completa pérdida de recursos!

Este es el lugar perfecto para la carga perezosa porque si la propiedad de la Orden es perezosa no irá a buscar todos los pedidos del cliente a menos que realmente los necesite. Puede enumerar los objetos del Cliente obteniendo solo su nombre y número de teléfono mientras la propiedad de la Orden está durmiendo pacientemente, lista para cuando la necesite.