Kapan saya harus menggunakan Malas <T>?

327

Saya menemukan artikel ini tentang Lazy: Kemalasan dalam C # 4.0 - Malas

Apa praktik terbaik untuk mendapatkan kinerja terbaik menggunakan objek Malas? Bisakah seseorang mengarahkan saya ke penggunaan praktis dalam aplikasi nyata? Dengan kata lain, kapan saya harus menggunakannya?

danyolgiax
sumber
42
Menggantikan: get { if (foo == null) foo = new Foo(); return foo; }. Dan ada zillions tempat yang memungkinkan untuk menggunakannya ...
Kirk Woll
57
Perhatikan bahwa get { if (foo == null) foo = new Foo(); return foo; }tidak aman untuk thread, sementara Lazy<T>thread-safe secara default.
Matius
23
Dari MSDN: PENTING: Inisialisasi malas adalah thread-safe, tetapi tidak melindungi objek setelah pembuatan. Anda harus mengunci objek sebelum mengaksesnya, kecuali jika jenisnya aman.
Pedro. Anak

Jawaban:

237

Anda biasanya menggunakannya ketika Anda ingin instantiate sesuatu pertama kali itu benar-benar digunakan. Ini menunda biaya pembuatannya sampai jika / saat dibutuhkan alih-alih selalu menimbulkan biaya.

Biasanya ini lebih disukai ketika objek mungkin atau mungkin tidak digunakan dan biaya membangun itu tidak sepele.

James Michael Hare
sumber
121
mengapa tidak SELALU menggunakan Malas?
TruthOf42
44
Ini menimbulkan biaya pada penggunaan pertama dan mungkin menggunakan beberapa penguncian overhead (atau mengorbankan keselamatan utas jika tidak) untuk melakukannya. Dengan demikian, harus dipilih dengan hati-hati dan tidak digunakan kecuali diperlukan.
James Michael Hare
3
James, bisakah Anda memperluas "dan biaya membangun tidak sepele"? Dalam kasus saya, saya memiliki 19 properti di kelas saya dan dalam kebanyakan kasus hanya 2 atau 3 yang perlu dilihat. Karena itu saya mempertimbangkan untuk mengimplementasikan setiap properti menggunakan Lazy<T>. Namun, untuk membuat masing-masing properti saya melakukan interpolasi linier (atau interpolasi bilinear) yang cukup sepele tetapi memang memiliki beberapa biaya. (Apakah Anda akan menyarankan agar saya pergi dan melakukan eksperimen sendiri?)
Ben
3
James, dengan nasihat saya sendiri, saya melakukan percobaan sendiri. Lihat posting saya .
Ben
17
Anda mungkin ingin menginisialisasi / instantiate segalanya "selama" sistem start-up untuk mencegah latensi pengguna dalam throughput tinggi, sistem latensi rendah. Ini hanyalah salah satu dari banyak alasan untuk tidak "selalu" menggunakan Malas.
Derrick
126

Anda harus mencoba menghindari penggunaan lajang, tetapi jika Anda memang perlu melakukannya, Lazy<T>membuat penerapan lajang yang malas dan aman di thread:

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;
}
Matius
sumber
38
Saya benci membaca Anda harus mencoba untuk menghindari penggunaan Singletons ketika saya menggunakannya: D ... sekarang saya perlu belajar mengapa saya harus mencoba menghindarinya: D
Bart Calixto
24
Saya akan berhenti menggunakan Singletons ketika Microsoft berhenti menggunakannya dalam contoh mereka.
eaglei22
4
Saya cenderung tidak setuju dengan gagasan perlunya menghindari Lajang. Saat mengikuti paradigma injeksi ketergantungan, seharusnya tidak masalah. Idealnya, semua dependensi Anda hanya boleh dibuat satu kali. Ini mengurangi tekanan pada GC dalam skenario beban tinggi. Karena itu, menjadikan mereka Singleton dari dalam kelas itu sendiri tidak masalah. Sebagian besar (jika tidak semua) wadah DI modern dapat menanganinya dengan cara apa pun yang Anda pilih.
Lee Grissom
1
Anda tidak perlu menggunakan pola singleton seperti itu, alih-alih gunakan wadah di mana saja konfigurasikan kelas Anda untuk singleton. Wadah akan mengurus overhead untuk Anda.
VivekDev
Semuanya memiliki tujuan, ada situasi di mana lajang adalah pendekatan yang baik dan situasi di mana tidak :).
Hawkzey
86

Sebuah contoh dunia nyata yang hebat di mana lazy loading berguna adalah dengan ORM (Object Relation Mappers) seperti Entity Framework dan NHibernate.

Katakanlah Anda memiliki entitas Pelanggan yang memiliki properti untuk Nama, Nomor Telepon, dan Pesanan. Nama dan Nomor Telepon adalah string biasa tetapi Pesanan adalah properti navigasi yang mengembalikan daftar setiap pesanan yang pernah dibuat pelanggan.

Anda mungkin sering ingin mengunjungi semua pelanggan Anda dan mendapatkan nama dan nomor telepon mereka untuk menelepon mereka. Ini adalah tugas yang sangat cepat dan sederhana, tetapi bayangkan jika setiap kali Anda membuat pelanggan secara otomatis pergi dan melakukan kompleks bergabung untuk mengembalikan ribuan pesanan. Bagian terburuknya adalah Anda bahkan tidak akan menggunakan pesanan sehingga itu adalah pemborosan sumber daya!

Ini adalah tempat yang tepat untuk pemuatan malas karena jika properti Order malas, tidak akan mengambil semua pesanan pelanggan kecuali Anda benar-benar membutuhkannya. Anda dapat menghitung objek Pelanggan hanya mendapatkan Nama dan Nomor Telepon mereka saat properti Order dengan sabar tidur, siap ketika Anda membutuhkannya.

Despertar
sumber
34
Contoh buruk, karena lazy loading seperti itu biasanya sudah dimasukkan ke dalam ORM. Anda seharusnya tidak mulai menambahkan nilai Malas <T> ke POCO Anda untuk mendapatkan pemuatan yang malas, tetapi gunakan cara spesifik ORM untuk melakukan itu.
Dynalon
56
@Dyna Contoh ini merujuk pada pemuatan malas bawaan dari ORM karena saya pikir ini mencontohkan kegunaan pemuatan malas dengan cara yang jelas dan sederhana.
Despertar
JADI, jika Anda menggunakan Entity Framework haruskah seseorang memaksakan malasnya sendiri? Atau apakah EF melakukannya untuk Anda?
Zapnologica
7
@Zapnologica EF melakukan semua ini untuk Anda secara default. Bahkan, jika Anda ingin memuat dengan penuh semangat (kebalikan dari pemuatan malas), Anda harus memberi tahu EF secara eksplisit dengan menggunakan Db.Customers.Include("Orders"). Ini akan menyebabkan pesanan bergabung dieksekusi pada saat itu daripada saat Customer.Ordersproperti pertama kali digunakan. Pemuatan Malas juga dapat dinonaktifkan melalui DbContext.
Despertar
2
Sebenarnya ini adalah contoh yang baik, karena orang mungkin ingin menambahkan fungsionalitas ini ketika menggunakan sesuatu seperti Dapper.
tbone
41

Saya telah mempertimbangkan untuk menggunakan Lazy<T>properti untuk membantu meningkatkan kinerja kode saya sendiri (dan untuk belajar lebih banyak tentangnya). Saya datang ke sini mencari jawaban tentang kapan menggunakannya tetapi tampaknya ke mana pun saya pergi ada frasa seperti:

Gunakan inisialisasi malas untuk menunda pembuatan objek besar atau sumber daya intensif, atau pelaksanaan tugas sumber daya intensif, terutama ketika penciptaan atau eksekusi tersebut mungkin tidak terjadi selama masa program.

dari MSDN Lazy <T> Class

Saya sedikit bingung karena saya tidak yakin di mana harus menarik garis. Sebagai contoh, saya menganggap interpolasi linier sebagai perhitungan yang cukup cepat tetapi jika saya tidak perlu melakukannya, dapatkah inisialisasi malas membantu saya menghindari melakukannya dan apakah itu layak?

Pada akhirnya saya memutuskan untuk mencoba tes saya sendiri dan saya pikir saya akan membagikan hasilnya di sini. Sayangnya saya tidak benar-benar ahli dalam melakukan tes semacam ini dan jadi saya senang mendapatkan komentar yang menyarankan perbaikan.

Deskripsi

Untuk kasus saya, saya sangat tertarik untuk melihat apakah Properti Malas dapat membantu meningkatkan bagian dari kode saya yang melakukan banyak interpolasi (sebagian besar tidak digunakan) dan jadi saya telah membuat tes yang membandingkan 3 pendekatan.

Saya membuat kelas tes terpisah dengan 20 properti uji (sebut saja properti-t) untuk setiap pendekatan.

  • GetInterp Class: Menjalankan interpolasi linier setiap kali properti-t didapat.
  • Kelas InitInterp: Menginisialisasi sifat-t dengan menjalankan interpolasi linier untuk masing-masing di konstruktor. Dapatkan hanya mengembalikan dua kali lipat.
  • Kelas InitLazy: Mengatur properti-t sebagai properti Malas sehingga interpolasi linier dijalankan sekali saat properti pertama kali didapat. Mendapat berikutnya hanya akan mengembalikan ganda yang sudah dihitung.

Hasil tes diukur dalam ms dan rata-rata 50 instansi atau 20 properti dapatkan. Setiap tes kemudian dijalankan 5 kali.

Hasil Tes 1: Instansiasi (rata-rata 50 Instansiasi)

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

Hasil Tes 2: First Get (rata-rata 20 properti mendapat)

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

Hasil Uji 3: Get Kedua (rata-rata 20 properti mendapat)

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

Pengamatan

GetInterplebih cepat untuk instantiate seperti yang diharapkan karena tidak melakukan apa pun. InitLazylebih cepat untuk instantiate daripada InitInterpmenyarankan bahwa overhead dalam mengatur properti malas lebih cepat daripada perhitungan interpolasi linier saya. Namun, saya agak bingung di sini karena InitInterpharus melakukan 20 interpolasi linier (untuk mengatur itu t-properti) tetapi hanya butuh 0,09 ms untuk instantiate (tes 1), dibandingkan dengan GetInterpyang membutuhkan 0,28 ms untuk melakukan hanya satu interpolasi linier pertama kali (tes 2), dan 0,1 ms untuk melakukannya kedua kalinya (tes 3).

Dibutuhkan InitLazyhampir 2 kali lebih lama daripada GetInterpuntuk mendapatkan properti pertama kali, sedangkan InitInterpyang tercepat, karena dihuni properti selama instantiasi. (Setidaknya itu yang seharusnya dilakukan tetapi mengapa hasil instantiasi jauh lebih cepat daripada interpolasi linier tunggal? Kapan tepatnya melakukan interpolasi ini?)

Sayangnya sepertinya ada beberapa optimasi kode otomatis yang terjadi dalam pengujian saya. Perlu waktu GetInterpyang sama untuk mendapatkan properti pertama kali seperti halnya yang kedua kalinya, tetapi itu menunjukkan lebih dari 2x lebih cepat. Sepertinya optimasi ini juga mempengaruhi kelas-kelas lain juga karena mereka semua mengambil jumlah waktu yang sama untuk pengujian 3. Namun, optimisasi semacam itu juga dapat terjadi dalam kode produksi saya sendiri yang mungkin juga menjadi pertimbangan penting.

Kesimpulan

Sementara beberapa hasil seperti yang diharapkan, ada juga beberapa hasil tak terduga yang sangat menarik mungkin karena optimisasi kode. Bahkan untuk kelas yang terlihat seperti mereka melakukan banyak pekerjaan di konstruktor, hasil instantiasi menunjukkan bahwa mereka mungkin masih sangat cepat untuk membuat, dibandingkan dengan mendapatkan properti ganda. Sementara para ahli di bidang ini mungkin dapat berkomentar dan menginvestigasi lebih menyeluruh, perasaan pribadi saya adalah bahwa saya perlu melakukan tes ini lagi tetapi pada kode produksi saya untuk memeriksa jenis optimisasi apa yang mungkin terjadi di sana juga. Namun, saya berharap itu InitInterpmungkin jalan yang harus ditempuh.

Ben
sumber
26
mungkin Anda harus memposting kode pengujian Anda untuk mereproduksi output Anda, karena tanpa mengetahui kode Anda akan sulit untuk menyarankan apa pun
WiiMaxx
1
Saya percaya trade-off utama adalah antara penggunaan memori (malas) dan penggunaan cpu (tidak-malas). Karena lazyharus melakukan beberapa pembukuan tambahan, InitLazyakan menggunakan lebih banyak memori daripada solusi lainnya. Mungkin juga memiliki hit kinerja kecil pada setiap akses, sementara memeriksa apakah sudah memiliki nilai atau tidak; Trik yang cerdik bisa menghilangkan overhead itu, tetapi akan membutuhkan dukungan khusus di IL. (Haskell melakukan ini dengan membuat setiap nilai malas panggilan fungsi; setelah nilai dihasilkan, itu diganti dengan fungsi yang mengembalikan nilai itu setiap waktu.)
jpaugh
14

Untuk menunjukkan contoh yang diposting oleh 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; }
    }
}

sebelum Malas lahir kita akan melakukannya dengan cara ini:

private static object lockingObject = new object();
public static LazySample InstanceCreation()
{
    if(lazilyInitObject == null)
    {
         lock (lockingObject)
         {
              if(lazilyInitObject == null)
              {
                   lazilyInitObject = new LazySample ();
              }
         }
    }
    return lazilyInitObject ;
}
Thulani Chivandikwa
sumber
6
Saya selalu menggunakan wadah IoC untuk ini.
Jowen
1
Saya sangat setuju untuk mempertimbangkan wadah IoC untuk ini. Namun jika Anda ingin objek tunggal malas diinisialisasi sederhana juga mempertimbangkan bahwa jika Anda tidak perlu ini aman thread melakukannya secara manual dengan Jika mungkin lebih baik mempertimbangkan overhead kinerja bagaimana Malas menangani sendiri.
Thulani Chivandikwa
12

Dari MSDN:

Gunakan instance Malas untuk menunda pembuatan objek besar atau sumber daya intensif atau pelaksanaan tugas sumber daya intensif, terutama ketika penciptaan atau eksekusi tersebut mungkin tidak terjadi selama masa program.

Selain jawaban James Michael Hare, Lazy memberikan inisialisasi aman untuk nilai Anda. Lihatlah enumerasi entri MSDN LazyThreadSafetyMode yang menjelaskan berbagai jenis mode keselamatan ulir untuk kelas ini.

Vasea
sumber
-2

Anda harus melihat contoh ini untuk memahami arsitektur 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");
    }
}

-> output -> 0 1 2

tetapi jika kode ini jangan tulis "list.Value.Add (0);"

output -> Nilai tidak dibuat

Bebek Tufy
sumber