언제 Lazy <T>를 사용해야합니까?


327

이 기사를 찾았습니다 Lazy: C # 4.0의 게으름 – 게으른

Lazy 객체를 사용하여 최상의 성능을 발휘하는 가장 좋은 방법은 무엇입니까? 누군가 실제 응용 프로그램에서 실용적으로 사용할 수 있습니까? 즉, 언제 사용해야합니까?


42
대체합니다 : get { if (foo == null) foo = new Foo(); return foo; }. 그리고 그것을 사용할 수있는 수십억의 장소가 있습니다 ...
Kirk Woll

57
get { if (foo == null) foo = new Foo(); return foo; }스레드로부터 안전하지 않습니다, 동안 Lazy<T>스레드 안전 기본으로합니다.
Matthew

23
MSDN : 중요 : 지연 초기화는 스레드로부터 안전하지만 생성 후 객체를 보호하지는 않습니다. 유형이 스레드로부터 안전하지 않은 한 액세스하기 전에 오브젝트를 잠 가야합니다.
Pedro.The.Kid

답변:


237

실제로 실제로 사용 된 무언가를 인스턴스화하려는 경우 일반적으로 사용합니다. 이렇게하면 항상 비용이 발생하지 않고 필요할 때 / 필요할 때까지 생성 비용이 지연됩니다.

일반적으로 이것은 객체가 사용되거나 사용되지 않을 수 있고 구성 비용이 사소하지 않을 때 바람직합니다.


121
왜 항상 Lazy를 사용하지 않습니까?
TruthOf42

44
처음 사용할 때 비용이 발생하며 잠금 오버 헤드를 사용하거나 그렇지 않은 경우 스레드 안전성을 희생 할 수 있습니다. 따라서 신중하게 선택해야하며 필요한 경우가 아니면 사용하지 마십시오.
제임스 마이클 헤어

3
제임스, "건설 비용이 사소하지 않다"고 확장 해 주시겠습니까? 내 경우에는 클래스에 19 개의 속성이 있으며 대부분의 경우 2 또는 3 만 살펴볼 필요가 있습니다. 따라서을 사용하여 각 속성을 구현하는 것을 고려하고 Lazy<T>있습니다. 그러나 각 속성을 만들려면 상당히 사소하지만 약간의 비용이 드는 선형 보간 (또는 이중 선형 보간)을 수행하고 있습니다. (내가 직접 실험 해 보라고 제안하겠습니까?)
Ben

3
제임스, 나 자신의 충고를 가지고 내 자신의 실험을했다. 내 게시물을 참조하십시오 .
Ben

17
처리량이 많고 대기 시간이 짧은 시스템에서 사용자 대기 시간을 방지하기 위해 시스템 시작시 "시작하는 동안"모든 항목을 초기화 / 인스턴스화 할 수 있습니다. 이것은 "항상"Lazy를 사용하지 않는 많은 이유 중 하나입니다.
데릭

126

싱글 톤 사용을 피해야하지만, 필요한 경우 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;
}

38
난 당신이 내가 그들을 사용하고 때 싱글 톤을 사용하지 않는 것을 시도해야 읽기 싫어 : D는 ... 지금은 내가 그들을 피하려고해야하는 이유를 배울 필요 : D
바트 Calixto

24
Microsoft가 예제에서 싱글 톤 사용을 중지하면 싱글 톤 사용을 중단합니다.
eaglei22

4
나는 싱글 톤을 피해야한다는 개념에 동의하지 않는 경향이 있습니다. 의존성 주입 패러다임을 따르는 경우 어느 쪽이든 중요하지 않습니다. 이상적으로는 모든 종속성이 한 번만 작성되어야합니다. 이는 고부하 시나리오에서 GC에 대한 압력을 줄입니다. 따라서 클래스 자체에서 싱글 톤으로 만드는 것이 좋습니다. 대부분의 현대 DI 용기는 원하는 방식으로 처리 할 수 ​​있습니다.
Lee Grissom

1
당신은 그런 싱글 톤 패턴을 사용할 필요가 없으며 대신 싱글 컨테이너를 위해 클래스를 구성하는 모든 컨테이너를 사용하십시오. 컨테이너가 오버 헤드를 처리합니다.
VivekDev

모든 것이 목적이 있습니다. 싱글 톤이 좋은 접근 방식과 그렇지 않은 상황이 있습니다. :).
Hawkzey

86

실제 게으른 로딩이 편리 경우의 예는 엔티티 프레임 워크와 NHibernate에 같은 ORM의 (객체 관계 매퍼)으로한다.

이름, 전화 번호 및 주문에 대한 특성이있는 엔티티 고객이 있다고 가정하십시오. Name 및 PhoneNumber는 일반 문자열이지만 Orders는 고객이 만든 모든 주문 목록을 반환하는 탐색 속성입니다.

당신은 종종 모든 고객을 거치고 그들의 이름과 전화 번호를 얻어서 전화하기를 원할 것입니다. 이것은 매우 빠르고 간단한 작업이지만 고객을 만들 때마다 자동으로 진행되어 복잡한 주문을 처리하여 수천 건의 주문을 반환한다고 상상해보십시오. 최악의 부분은 주문을 사용하지 않아서 완전한 자원 낭비라는 것입니다!

Order 속성이 게으 르면 실제로 필요한 경우가 아니라면 모든 고객의 주문을 가져 오지 않기 때문에 이것은 지연 로딩에 완벽한 장소입니다. Order 속성이 참을성있게 잠자기 상태 일 때 필요할 때 사용할 수 있도록 이름과 전화 번호 만받는 고객 개체를 열거 할 수 있습니다.


34
이러한 게으른 로딩은 일반적으로 이미 ORM에 내장되어 있으므로 나쁜 예입니다. 지연 로딩을 얻기 위해 POCO에 Lazy <T> 값을 추가하지 말고 ORM 특정 방식을 사용하십시오.
Dynalon

56
@Dyna이 예제는 ORM의 내장 지연 로딩을 참조하고 있는데, 이는 명확하고 간단한 방법으로 지연 로딩의 유용성을 보여줄 것이기 때문입니다.
Despertar

Entity Framework를 사용하는 경우 자체 게으름을 강요해야합니까? 아니면 EF가 당신을 위해 그것을합니까?
Zapnologica

7
@Zapnologica EF는 기본적으로이 모든 기능을 제공합니다. 사실, 간절한 로딩 (게으른 로딩의 반대)을 원하면을 사용하여 EF에 명시 적으로 알려야합니다 Db.Customers.Include("Orders"). 이로 인해 Customer.Orders속성을 처음 사용할 때가 아니라 해당 시점에 주문 조인이 실행 됩니다. 지연 로딩은 DbContext를 통해 비활성화 할 수도 있습니다.
Despertar

2
실제로 이것은 Dapper와 같은 것을 사용할 때이 기능을 추가 할 수 있기 때문에 좋은 예입니다.
tbone

41

Lazy<T>내 코드의 성능을 향상시키고 약간 더 배우기 위해 속성 사용을 고려하고 있습니다. 나는 그것을 언제 사용 해야하는지에 대한 답을 찾기 위해 여기에 왔지만 어디를 가든지 다음과 같은 문구가있는 것 같습니다.

지연 초기화를 사용하여 특히 프로그램 수명 기간 동안 이러한 생성 또는 실행이 발생하지 않을 때 큰 또는 리소스 집약적 인 개체 생성 또는 리소스 집약적 작업 실행을 연기하십시오.

에서 MSDN 게으른 <T> 클래스

선을 그릴 위치가 확실하지 않기 때문에 약간 혼란스러워합니다. 예를 들어 선형 보간법을 상당히 빠른 계산으로 생각하지만 필요하지 않으면 지연 초기화를 수행하지 않아도 가치가 있습니까?

결국 나는 내 자신의 테스트를 시도하기로 결정했고 여기에서 결과를 공유 할 것이라고 생각했습니다. 불행히도 나는 이런 종류의 테스트를 수행하는 전문가가 아니므로 개선을 제안하는 의견을 기쁘게 생각합니다.

기술

필자의 경우 Lazy Properties가 많은 보간을 수행하는 코드의 일부를 개선하는 데 도움이되는지 (특히 대부분 사용되지 않음) 관심이 있었으므로 3 가지 접근법을 비교하는 테스트를 만들었습니다.

각 접근 방식마다 20 개의 테스트 속성 (t 속성이라고 함)으로 별도의 테스트 클래스를 만들었습니다.

  • GetInterp 클래스 : t 속성을 얻을 때마다 선형 보간을 실행합니다.
  • InitInterp 클래스 : 생성자에서 각각에 대한 선형 보간을 실행하여 t- 속성을 초기화합니다. get은 단지 double을 반환합니다.
  • InitLazy 클래스 : 속성을 처음 가져올 때 선형 보간이 한 번 실행되도록 t 속성을 지연 속성으로 설정합니다. 후속 Get은 이미 계산 된 Double을 반환해야합니다.

테스트 결과는 ms 단위로 측정되며 평균 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 개의 속성 가져 오기)

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 결과 : 두 번째 가져 오기 (평균 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제안하는 것보다 인스턴스화 하는 것이 더 빠릅니다. 그러나 InitInterp20 개의 선형 보간을 수행해야 하기 때문에 (t 속성을 설정해야 함) 여기에서 약간 혼란 스럽지만 인스턴스화 (테스트 1) GetInterp하는 데 0.09ms 만 걸리는 반면 한 번의 선형 보간을 수행하는 데 0.28ms가 소요됩니다 첫 번째 (테스트 2), 두 번째 (테스트 3)에는 0.1ms입니다.

처음 속성을 얻는 InitLazy것보다 거의 2 배 더 오래 걸리며 인스턴스화하는 동안 속성을 채웠기 때문에 가장 빠릅니다. (적어도 그것이해야 할 일이지만 왜 단일 선형 보간보다 인스턴스화 결과가 훨씬 빠릅니까? 정확히 언제 보간을 수행합니까?)GetInterpInitInterp

불행히도 테스트에서 자동 코드 최적화가 진행되는 것처럼 보입니다. GetInterp두 번째와 마찬가지로 처음으로 속성을 얻는 데는 시간이 걸리지 만 2 배 이상 빠릅니다. 이 최적화는 다른 클래스에도 영향을 미치는 것으로 보입니다. 모두 테스트 3에 거의 동일한 시간이 걸리기 때문입니다. 그러나 이러한 최적화는 또한 내 자신의 프로덕션 코드에서 발생할 수 있으며 중요한 고려 사항이 될 수도 있습니다.

결론

일부 결과는 예상대로이지만 코드 최적화로 인해 예상치 못한 매우 흥미로운 결과도 있습니다. 생성자에서 많은 작업을 수행하는 것처럼 보이는 클래스의 경우에도 인스턴스화 결과에 따르면 이중 속성을 얻는 것보다 훨씬 빠르게 생성 할 수 있습니다. 이 분야의 전문가가 더 철저하게 논평하고 조사 할 수는 있지만 개인적 느낌은이 테스트를 다시 수행해야하지만 프로덕션 코드에서 어떤 종류의 최적화가 수행되고 있는지 조사하기위한 것입니다. 그러나 나는 그것이 InitInterp갈 길이 라고 기대하고 있습니다.


26
코드를 모른 채 열심히되기 때문에 어쩌면 당신은 아무것도 제안하고, 당신의 출력을 재현하기위한 테스트 코드를 게시해야
WiiMaxx

1
주요 단점은 메모리 사용 (게으른)과 CPU 사용 (비 게으른) 사이에 있다고 생각합니다. 때문에 lazy몇 가지 추가 부기을 할 수 있으며, InitLazy다른 솔루션보다 더 많은 메모리를 사용합니다. 또한 액세스 할 때마다 약간의 성능 저하가있을 수 있으며 이미 값이 있는지 확인합니다. 영리한 트릭으로 인해 오버 헤드가 제거 될 수 있지만 IL에서는 특별한 지원이 필요합니다. (Haskell은 모든 게으른 값을 함수 호출로하여이를 수행합니다. 일단 값이 생성되면 매번 해당 값을 반환하는 함수로 대체됩니다.)
jpaugh

14

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

6
나는 항상 이것을 위해 IoC 컨테이너를 사용합니다.
Jowen

1
나는 이것을 위해 IoC 컨테이너를 고려하는 것에 강력히 동의합니다. 그러나 간단한 게으른 초기화 된 객체 싱글 톤을 원한다면 스레드 안전이 필요하지 않은 경우 If를 사용하여 수동으로 수행하면 Lazy가 자체 처리하는 방법의 성능 오버 헤드를 고려하는 것이 가장 좋습니다.
Thulani Chivandikwa

12

MSDN에서 :

Lazy 인스턴스를 사용하여 특히 프로그램 수명 기간 동안 이러한 작성 또는 실행이 수행되지 않을 때 큰 또는 자원 집약적 오브젝트 작성 또는 자원 집약적 태스크 실행을 지연하십시오.

James Michael Hare의 답변 외에도 Lazy는 값에 대한 스레드 안전 초기화를 제공합니다. 이 클래스에 대한 다양한 유형의 스레드 안전 모드를 설명하는 LazyThreadSafetyMode 열거 MSDN 항목을 살펴보십시오 .


-2

Lazy Loading 아키텍처를 이해하려면이 예제를보아야합니다.

private readonly Lazy<List<int>> list = new Lazy<List<int>>(() =>
{
    List<int> configList = new List<int>(Thread.CurrentThread.ManagedThreadId);
    return configList;
});
public void Execute()
{
    list.Value.Add(0);
    if (list.IsValueCreated)
    {
        list.Value.Add(1);
        list.Value.Add(2);

        foreach (var item in list.Value)
        {
            Console.WriteLine(item);
        }
    }
    else
    {
        Console.WriteLine("Value not created");
    }
}

-> 출력-> 0 1 2

그러나이 코드가 "list.Value.Add (0);"을 쓰지 않으면

출력-> 값이 생성되지 않았습니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.