Когда я должен использовать Lazy <T>? - PullRequest
293 голосов
/ 27 июля 2011

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

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

Ответы [ 6 ]

211 голосов
/ 27 июля 2011

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

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

113 голосов
/ 27 июля 2011

Вы должны стараться избегать использования Singletons, но если вам это когда-нибудь понадобится, 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;
}
78 голосов
/ 27 января 2013

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

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

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

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

37 голосов
/ 01 февраля 2014

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

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

из MSDN Lazy Класс

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

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

Описание

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

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

  • GetInterp Class: Запускает линейную интерполяцию при каждом получении t-свойства.
  • InitInterp Class: Инициализирует t-свойства, выполняя линейную интерполяцию для каждого в конструкторе.Метод get просто возвращает значение типа double.
  • InitLazy Class: Устанавливает t-свойства как свойства Lazy, чтобы линейная интерполяция запускалась один раз при первом получении свойства.Последующие получения должны просто возвращать уже рассчитанный 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 результата: First Get (в среднем из 20 объектов получает)

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: Second 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, предполагая, что накладные расходы при настройке свойств lazy быстрее, чем мои вычисления с линейной интерполяцией.Тем не менее, я немного запутался, потому что InitInterp должен выполнять 20 линейных интерполяций (чтобы установить его t-свойства), но для его создания (тест 1) требуется всего 0,09 мс, по сравнению с GetInterp, который занимает 0,28 мсвыполнить только одну линейную интерполяцию в первый раз (тест 2) и 0,1 мс, чтобы сделать это во второй раз (тест 3).

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

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

Выводы

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

14 голосов
/ 06 июня 2013

Просто чтобы указать на пример, опубликованный Мэтью

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 ;
}
12 голосов
/ 27 июля 2011

Из MSDN:

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

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

...