Когда я должен использовать Lazy?

Я нашел эту статью о Lazy: лень в C# 4.0-Lazy

какова наилучшая практика для лучшей производительности с использованием ленивых объектов? Может ли кто-нибудь указать мне на практическое использование в реальном приложении? Другими словами, когда я должен использовать его?

6 ответов


вы обычно используете его, когда хотите создать экземпляр чего-то в первый раз, когда он фактически используется. Это задерживает стоимость его создания до тех пор, пока/когда это необходимо, а не всегда несет расходы.

обычно это предпочтительнее, когда объект может или не может быть использован, а стоимость его построения нетривиальна.


вы должны стараться избегать использования Синглетов, но если вам когда-нибудь понадобится,Lazy<T> упрощает реализацию ленивых, потокобезопасных синглетов:

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

большой реальных пример того, где ленивая загрузка пригодится, - это ORM (Object Relation Mappers), такие как Entity Framework и NHibernate.

скажем, у вас есть клиент сущности, который имеет свойства для имени, номера телефона и заказов. Name и PhoneNumber-это обычные строки, но Orders-это свойство навигации, которое возвращает список всех заказов, когда-либо сделанных клиентом.

вы часто можете захотеть пройти через всех ваших клиентов и получить их имя и номер телефона для вызова. Это очень быстрая и простая задача, но представьте, что каждый раз, когда вы создали клиента, он автоматически пошел и сделал сложное соединение, чтобы вернуть тысячи заказов. хуже всего то, что вы даже не собираетесь использовать заказы, так что это полная трата ресурсов!

Это идеальное место для ленивой загрузки, потому что, если свойство заказа лениво, оно не будет получать весь заказ клиента, если они вам не нужны. Вы может перечислять объекты клиента, получая только их имя и номер телефона, пока свойство заказа терпеливо спит, готовое к тому, когда вам это нужно.


я рассматривал возможность использования Lazy<T> свойства, чтобы помочь улучшить производительность моего кода (и узнать немного больше об этом). Я пришел сюда в поисках ответов о том, когда его использовать, но кажется, что везде, куда я иду, есть такие фразы, как:

используйте ленивую инициализацию, чтобы отложить создание большого или ресурсоемкий объект, или выполнение ресурсоемкой задача, особенно если такое создание или выполнение может не произойти во время срок службы программы.

С MSDN ленивый класс

я остался немного смущен, потому что я не уверен, где провести линию. Например, я рассматриваю линейную интерполяцию как довольно быстрое вычисление, но если мне это не нужно, Может ли ленивая инициализация помочь мне избежать этого, и стоит ли это того?

в конце концов я решил попробовать свой собственный тест, и я думал, что поделюсь результатами здесь. К сожалению, нет. действительно эксперт в проведении такого рода тестов, и поэтому я рад получить комментарии, которые предлагают улучшения.

описание

для моего случая мне было особенно интересно посмотреть, могут ли ленивые свойства помочь улучшить часть моего кода, которая делает много интерполяции (большая часть ее не используется), и поэтому я создал тест, который сравнивал 3 подхода.

я создал отдельный тестовый класс с 20 тестовыми свойствами (назовем их Т-свойства) для каждого подхода.

  • Класс GetInterp: запускает линейную интерполяцию каждый раз, когда T-свойство получено.
  • Класс InitInterp: инициализирует t-свойства, выполняя линейную интерполяцию для каждого из них в конструкторе. Get просто возвращает двойной.
  • InitLazy Класс: устанавливает t-свойства как ленивые свойства, так что линейная интерполяция выполняется один раз, когда свойство первый попался. Последующие gets должны просто возвращать уже вычисленный double.

результаты теста измеряются в МС и составляют в среднем 50 экземпляров или 20 свойств. Затем каждый тест запускался 5 раз.

Результаты Теста 1: экземпляр (в среднем 50 экземпляров)

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

Результаты Теста 2: сначала получить (в среднем 20 собственность gets)

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

Результаты Теста 3: второй Get (в среднем 20 свойство получает)

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

замечания

GetInterp быстрее всего создать экземпляр, как ожидалось, потому что он ничего не делает. InitLazy быстрее создать, чем InitInterp предполагая, что накладные расходы при настройке ленивых свойств быстрее, чем мой расчет линейной интерполяции. Тем не менее, я немного смущен вот так InitInterp должен делать 20 линейных интерполяций (для настройки его t-свойств), но для создания экземпляра требуется всего 0,09 МС (тест 1), по сравнению с GetInterp что занимает 0,28 мс, чтобы сделать только одну линейную интерполяцию в первый раз (тест 2) и 0,1 мс, чтобы сделать это во второй раз (тест 3).

проходит InitLazy почти в 2 раза больше, чем GetInterp чтобы получить свойство в первый раз, в то время как InitInterp является самым быстрым, потому что он заполняет свои свойства во время создания экземпляра. (На по крайней мере, это то, что он должен был сделать, но почему результат создания экземпляра был намного быстрее, чем одна линейная интерполяция? Когда именно он делает эти выводы?)

к сожалению, похоже, что в моих тестах происходит автоматическая оптимизация кода. Следует взять GetInterp в то же время, чтобы получить свойство в первый раз, как и во второй раз, но он показывает, как более 2x быстрее. Похоже, что эта оптимизация также влияет на другую классы также, так как все они занимают примерно одинаковое количество времени для теста 3. Однако такие оптимизации могут также иметь место в моем собственном производственном коде, что также может быть важным соображением.

выводы

хотя некоторые результаты ожидаются, есть также некоторые очень интересные неожиданные результаты, вероятно, из-за оптимизации кода. Даже для классов, которые выглядят так, как будто они делают много работы в конструкторе, результаты создания экземпляров показывают, что они все еще могут быть очень быстрыми для создания, по сравнению с получением двойного свойства. Хотя эксперты в этой области могут комментировать и исследовать более тщательно, мое личное чувство заключается в том, что мне нужно сделать этот тест снова, но на моем производственном коде, чтобы изучить, какие оптимизации могут иметь место там тоже. Однако я ожидаю этого InitInterp может быть, это путь.


просто указать на пример, опубликованный 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; }
    }
}

прежде чем ленивый родился, мы сделали бы это так:

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

из MSDN:

используйте экземпляр Lazy, чтобы отложить создание большого или ресурсоемкого объекта или выполнение ресурсоемкой задачи, особенно если такое создание или выполнение не может произойти в течение срока службы программы.

в дополнение к ответу Джеймса Майкла Хара, Lazy обеспечивает потокобезопасную инициализацию вашего значения. Взгляните на LazyThreadSafetyMode запись перечисления MSDN, описывающая различные типы режимов безопасности резьбы для данного класса.