Ne zaman Lazy <T> kullanmalıyım?


327

Bu makaleyi buldum Lazy: C # 4.0 tembellik - Tembel

Lazy nesnelerini kullanarak en iyi performansı elde etmek için en iyi uygulama hangisidir? Birisi beni gerçek bir uygulamada pratik bir kullanıma yönlendirebilir mi? Başka bir deyişle, ne zaman kullanmalıyım?


42
Yerini: get { if (foo == null) foo = new Foo(); return foo; }. Ve onu kullanmak için olası yerler var ...
Kirk Woll

57
İş get { if (foo == null) foo = new Foo(); return foo; }parçacığı için güvenli değil, Lazy<T>varsayılan olarak iş parçacığı için güvenli olduğunu unutmayın.
Matthew

23
MSDN'den: ÖNEMLİ: Tembel başlatma iş parçacığı için güvenlidir, ancak oluşturulduktan sonra nesneyi korumaz. Nesne, iş parçacığı için güvenli değilse, erişmeden önce kilitlemelisiniz.
Pedro.The.Kid

Yanıtlar:


237

Genellikle bir şeyi ilk kez kullanıldığında başlatmak istediğinizde kullanırsınız. Bu, maliyeti her zaman yapmak yerine gerekip gerekmediği zamana kadar oluşturma maliyetini geciktirir.

Genellikle bu, nesne kullanılabileceği veya kullanılamayacağı ve nesneyi inşa etmenin maliyeti önemsiz olduğunda tercih edilir.


121
neden DAİMA Lazy kullanmıyorsunuz?
2013

44
İlk kullanımda maliyeti doğurur ve bunu yapmak için bir miktar kilitleme yükü kullanabilir (veya diş güvenliğinden ödün vermez). Bu nedenle dikkatlice seçilmeli ve gerekmedikçe kullanılmamalıdır.
James Michael Hare

3
James, lütfen "ve inşa etmenin maliyeti önemsiz değil" konusunu genişletebilir misiniz? Benim durumumda sınıfımda 19 mülk var ve çoğu durumda sadece 2 veya 3'e bakılması gerekecek. Bu nedenle her özelliği kullanarak uygulamayı düşünüyorum Lazy<T>. Ancak, her özelliği oluşturmak için oldukça önemsiz olan ancak bir miktar maliyeti olan doğrusal bir enterpolasyon (veya bir çift doğrusal enterpolasyon) yapıyorum. (Gidip kendi
Ben

3
James, kendi tavsiyemi alarak kendi deneyimi yaptım. Yayına bakın .
Ben

17
Yüksek verimli, düşük gecikmeli sistemlerde kullanıcı gecikmesini önlemek için sistem başlangıcı sırasında "her şeyi" başlatmak / başlatmak isteyebilirsiniz. Bu, Lazy'yi "her zaman" kullanmamak için birçok nedenden sadece bir tanesidir.
Derrick

126

Singletons kullanmaktan kaçınmaya çalışmalısınız, ancak ihtiyacınız varsa Lazy<T>tembel, iş parçacığı açısından güvenli singletonları uygulamayı kolaylaştırır:

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
Okumaktan nefret ediyorum Onları kullanırken Singletons kullanmaktan kaçınmaya çalışmalısınız: D ... şimdi onlardan neden kaçınmaya çalışmam gerektiğini öğrenmem gerekiyor: D
Bart Calixto

24
Microsoft bunları örneklerinde kullanmayı bıraktığında Singletons'u kullanmayı bırakacağım.
eaglei22

4
Singletons'dan kaçınmaya ihtiyaç duyduğu fikrine katılmıyorum. Bağımlılık enjeksiyon paradigmasını izlerken, her iki şekilde de önemli olmamalıdır. İdeal olarak, tüm bağımlılıklarınız sadece bir kez yaratılmalıdır. Bu, yüksek yük senaryolarında GC üzerindeki basıncı azaltır. Bu nedenle, onları sınıfın içinden bir Singleton yapmak iyidir. Çoğu (hepsi değilse de) modern DI kapları her iki şekilde de kullanabilir.
Lee Grissom

1
Bunun gibi tek bir desen kullanmanız gerekmez, bunun yerine sınıfınızı singleton için yapılandırmak için herhangi bir di kap kullanın. Konteyner yükü sizin için halledecektir.
VivekDev

Her şeyin bir amacı vardır, singletonların iyi bir yaklaşım olduğu durumlar ve olmadığı durumlar vardır :).
Hawkzey

86

Tembel yüklemenin kullanışlı olduğu gerçek dünyaya harika bir örnek, Entity Framework ve NHibernate gibi ORM'lerle (Nesne İlişkisi Eşleştiricileri) verilebilir.

Diyelim ki Name, PhoneNumber ve Orders özelliklerine sahip bir varlığınız var. Name ve PhoneNumber normal dizelerdir ancak Orders, müşterinin şimdiye kadar yaptığı her siparişin listesini döndüren bir gezinme özelliğidir.

Sıklıkla tüm müşterinizin üzerinden geçmek ve onları aramak için adlarını ve telefon numaralarını almak isteyebilirsiniz. Bu çok hızlı ve basit bir iştir, ancak her müşteri oluşturduğunuzda otomatik olarak gittiğini ve binlerce siparişi iade etmek için karmaşık bir birleşim yaptığını hayal edin. En kötü yanı, siparişleri kullanmayacağınız için kaynakların tamamen boşa harcanmasıdır!

Bu, tembel yükleme için mükemmel bir yerdir, çünkü Order özelliği tembel ise, gerçekten ihtiyacınız olmadıkça tüm müşterinin siparişini almaya gitmez. Order özelliği sabırla uyurken, ihtiyacınız olduğunda hazır olan Müşteri nesnelerini yalnızca Adlarını ve Telefon Numaralarını alacak şekilde numaralandırabilirsiniz.


34
Kötü bir örnek, bu tür tembel yükleme genellikle ORM'de zaten yerleşik olduğundan. Tembel yükleme yapmak için POCO'larınıza Lazy <T> değerleri eklemeye başlamamalısınız, ancak bunu yapmak için ORM'ye özgü yolu kullanın.
Dynalon

56
@Dyna Bu örnek bir ORM'nin yerleşik tembel yüklemesine atıfta bulunur, çünkü bunun tembel yüklemenin kullanışlılığını açık ve basit bir şekilde örneklediğini düşünüyorum.
Despertar

Öyleyse Entity Framework'ten faydalanıyorsanız, kendi tembelliğini zorlamalı mı? Yoksa EF bunu sizin için yapıyor mu?
Zapnologica

7
@Zapnologica EF bunları sizin için varsayılan olarak yapar. Aslında, istekli yükleme yapmak istiyorsanız (tembel yüklemenin tersi), EF'yi açıkça kullanarak söylemelisiniz Db.Customers.Include("Orders"). Bu, sipariş birleştirmenin Customer.Ordersözelliğin ilk kullanıldığı zaman yerine o anda yürütülmesine neden olur . Tembel Yükleme, DbContext aracılığıyla da devre dışı bırakılabilir.
Despertar

2
Aslında bu iyi bir örnektir, çünkü Dapper gibi bir şey kullanırken bu işlevi eklemek isteyebilirsiniz.
tbone

41

Lazy<T>Kendi kod performansımı artırmak (ve bu konuda biraz daha fazla bilgi edinmek) için özellikleri kullanarak düşünüyor . Buraya ne zaman kullanılacağına dair cevaplar aramaya geldim ama gittiğim her yerde şöyle ifadeler var gibi görünüyor:

Büyük veya kaynak yoğun bir nesnenin oluşturulmasını veya kaynak yoğun bir görevin yürütülmesini ertelemek için, özellikle de programın ömrü boyunca bu tür bir oluşturma veya yürütme gerçekleşmeyebiliyorsa, tembel başlatma kullanın.

adlı MSDN Lazy <T> Sınıf

Biraz kafam karıştı çünkü çizgiyi nereye çizeceğimden emin değilim. Örneğin, doğrusal enterpolasyonu oldukça hızlı bir hesaplama olarak görüyorum, ancak yapmam gerekmiyorsa, tembel başlatma, bunu yapmaktan kaçınmama yardımcı olabilir ve buna değer mi?

Sonunda kendi testimi denemeye karar verdim ve sonuçları burada paylaşacağımı düşündüm. Ne yazık ki bu tür testleri yaparken gerçekten uzman değilim ve bu yüzden iyileştirmeler öneren yorumlar almaktan mutluluk duyuyorum.

Açıklama

Benim durumum için, özellikle Lazy Properties kodumun bir çok enterpolasyon (çoğu kullanılmayan) yapan bir bölümünü geliştirmeye yardımcı olup olmadığını görmek ilgimi çekti ve bu yüzden 3 yaklaşımları karşılaştıran bir test oluşturduk.

Her yaklaşım için 20 test özelliğine (onlara t-özellikleri diyelim) sahip ayrı bir test sınıfı oluşturdum.

  • GetInterp Class: Bir t özelliği her alındığında doğrusal enterpolasyonu çalıştırır.
  • InitInterp Class: Yapıcıdaki her biri için doğrusal enterpolasyonu çalıştırarak t özelliklerini başlatır. Get sadece bir çift döndürür.
  • InitLazy Class: Doğrusal enterpolasyon, özellik ilk alındığında bir kez çalıştırılacak şekilde t özelliklerini Lazy özellikleri olarak ayarlar. Müteakip kazanımlar sadece hesaplanmış bir çifte dönmelidir.

Test sonuçları ms cinsinden ölçülür ve ortalama 50 örnekleme veya 20 özellik elde etme ortalamasıdır. Her test daha sonra 5 kez gerçekleştirildi.

Test 1 Sonuçları: Örnekleme (ortalama 50 örnekleme)

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

Test 2 Sonuçları: İlk Getir (ortalama 20 mülk alır)

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

Test 3 Sonuçları: İkinci Getir (ortalama 20 mülk alır)

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

Gözlemler

GetInterpbeklendiği gibi somutlaştırmak en hızlısıdır çünkü hiçbir şey yapmaz. tembel özellikler InitLazyoluşturmanın InitInterpgetirdiği yükün doğrusal enterpolasyon hesaplamamdan daha hızlı olduğunu düşündürmekten daha hızlıdır. Bununla birlikte, burada biraz kafam karıştı çünkü InitInterp20 doğrusal enterpolasyon yapmalı (t özelliklerini ayarlamak için), ancak GetInterpsadece bir doğrusal enterpolasyon yapmak için 0.28 ms sürdüğüne kıyasla (test 1) sadece 0.09 ms sürüyor ilk kez (test 2) ve ikinci kez yapmak için 0.1 msn (test 3).

İlk InitLazykez GetInterpbir mülk edinmekten neredeyse 2 kat daha uzun sürer InitInterp, en hızlısıdır, çünkü örnekleme sırasında özelliklerini doldurur. (En azından yapması gereken buydu ama neden örnekleme tek bir doğrusal enterpolasyondan çok daha hızlıydı? Bu enterpolasyonları tam olarak ne zaman yapıyor?)

Ne yazık ki testlerimde bazı otomatik kod optimizasyonu var gibi görünüyor. Bu almalı GetInterpbir özellik o ikinci kez yaptığı gibi ilk kez almak için aynı zamanda, ancak daha hızlı daha fazla 2 kata gösteriyor. Bu optimizasyon aynı zamanda diğer sınıfları da etkiliyor gibi gözüküyor çünkü hepsi test 3 için aynı miktarda zaman alıyor. Ancak, bu tür optimizasyonlar kendi üretim kodumda da önemli bir husus olabilir.

Sonuçlar

Bazı sonuçlar beklendiği gibi olsa da, muhtemelen kod optimizasyonları nedeniyle çok ilginç beklenmedik sonuçlar da vardır. Yapıcıda çok fazla iş yapıyormuş gibi görünen sınıflar için bile, örnekleme sonuçları, çift özellik elde edilmesine kıyasla, hala çok hızlı oluşturulabileceğini gösteriyor. Bu alandaki uzmanlar daha ayrıntılı bir şekilde yorum yapabilir ve araştırabilirken, kişisel hissim, orada ne tür bir optimizasyonun olabileceğini incelemek için bu testi tekrar yapmam gerektiğidir. Ancak, InitInterpbunun bir yol olabileceğini umuyorum.


26
kodunuzu bilmeden herhangi bir şey önermek zor olacağından, belki de çıkışınızı yeniden üretmek için test kodunuzu göndermelisiniz
WiiMaxx

1
Ana değişimin bellek kullanımı (tembel) ve cpu kullanımı (tembel değil) arasında olduğuna inanıyorum. Çünkü lazybazı ekstra kitap tutma yapmak zorunda InitLazy, diğer çözümlere göre daha fazla bellek kullanır. Ayrıca, her erişimde küçük bir performans isabine sahip olabilirken, zaten bir değere sahip olup olmadığını kontrol eder; akıllı hileler bu yükü kaldırabilir, ancak IL'de özel destek gerektirir. (Haskell bunu her tembel değeri bir işlev çağrısı yaparak yapar; değer oluşturulduktan sonra, her seferinde bu değeri döndüren bir işlevle değiştirilir.)
jpaugh

14

Mathew tarafından yayınlanan örneğe işaret etmek için

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

Tembel doğmadan önce bunu şu şekilde yapardık:

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

6
Bunun için daima bir IoC konteyneri kullanıyorum.
Jowen

1
Bunun için bir IoC kapsayıcısını dikkate almayı çok seviyorum. Bununla birlikte, basit bir tembel başlangıç ​​nesnesi singletonu istiyorsanız, bunun manuel olarak iş parçacığı ile güvenli olması gerekmiyorsa, Lazy'nin kendini nasıl ele alacağının performans yükünü göz önünde bulundurarak en iyisi olabilir.
Thulani Chivandikwa

12

MSDN'den:

Büyük veya kaynak yoğun bir nesnenin oluşturulmasını veya kaynak yoğun bir görevin yürütülmesini ertelemek için bir Lazy örneği kullanın, özellikle de programın ömrü boyunca bu tür bir oluşturma veya yürütme gerçekleşmeyebiliyorsa.

James Michael Hare'nin cevabına ek olarak Lazy, değerinizin güvenli bir şekilde başlatılmasını sağlar. Bu sınıf için çeşitli iş parçacığı güvenlik modlarını tanımlayan LazyThreadSafetyMode numaralandırma MSDN girişine bakın .


-2

Tembel Yükleme mimarisini anlamak için bu örneğe bakmalısınız

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

-> çıktı -> 0 1 2

ancak bu kod "list.Value.Add (0);"

output -> Değer oluşturulmadı

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.