Znalazłem ten artykuł na temat Lazy
: Lenistwo w C # 4.0 - Leniwy
Jaka jest najlepsza praktyka, aby uzyskać najlepszą wydajność przy użyciu obiektów Lazy? Czy ktoś może wskazać mi praktyczne zastosowanie w prawdziwej aplikacji? Innymi słowy, kiedy powinienem go użyć?
c#
.net
lazy-evaluation
danyolgiax
źródło
źródło
get { if (foo == null) foo = new Foo(); return foo; }
. I jest zillions możliwych miejsc, aby z niego skorzystać ...get { if (foo == null) foo = new Foo(); return foo; }
nie jest bezpieczny dla wątków, podczas gdyLazy<T>
domyślnie jest bezpieczny dla wątków.Odpowiedzi:
Zazwyczaj używasz go, gdy chcesz utworzyć instancję czegoś przy pierwszym użyciu. Opóźnia to koszt tworzenia go do momentu, gdy jest potrzebny, zamiast zawsze ponosić koszty.
Zwykle jest to korzystne, gdy obiekt może być używany lub nie, a koszt jego budowy nie jest trywialny.
źródło
Lazy<T>
. Jednak, aby utworzyć każdą właściwość, wykonuję interpolację liniową (lub interpolację dwuliniową), która jest dość trywialna, ale wiąże się z pewnymi kosztami. (Czy zamierzasz zasugerować, żebym wybrał się na własny eksperyment?)Powinieneś starać się unikać korzystania z Singletonów, ale jeśli kiedykolwiek zajdzie taka potrzeba,
Lazy<T>
ułatwia wdrażanie leniwych, bezpiecznych dla wątków singletonów:źródło
Wielki świecie rzeczywistym przykładem gdzie leniwe ładowanie przydaje się z ORM (Object za Relacja odwzorowujące), takich jak Entity Framework i NHibernate.
Załóżmy, że masz klienta będącego klientem, który ma właściwości dotyczące nazwy, numeru telefonu i zamówień. Nazwa i numer telefonu są ciągami ciągłymi, ale Zamówienia to właściwość nawigacji, która zwraca listę wszystkich zamówień, które klient kiedykolwiek złożył.
Często możesz przejrzeć listę wszystkich klientów i uzyskać ich nazwę i numer telefonu, aby do nich zadzwonić. Jest to bardzo szybkie i proste zadanie, ale wyobraź sobie, że za każdym razem, gdy tworzysz klienta, automatycznie przechodzi on i wykonuje złożone połączenie, aby zwrócić tysiące zamówień. Najgorsze jest to, że nawet nie zamierzasz korzystać z zamówień, więc jest to marnowanie zasobów!
Jest to idealne miejsce do leniwego ładowania, ponieważ jeśli właściwość Order jest leniwa, nie pobierze całego zamówienia klienta, chyba że faktycznie ich potrzebujesz. Możesz wyliczyć obiekty klienta, otrzymując tylko ich nazwę i numer telefonu, gdy właściwość Zamówienie cierpliwie śpi i jest gotowa, gdy będziesz tego potrzebować.
źródło
Db.Customers.Include("Orders")
. Spowoduje to, że łączenie zamówienia zostanie wykonane w tym momencie, a nie przyCustomer.Orders
pierwszym użyciu właściwości. Leniwe ładowanie można również wyłączyć za pomocą DbContext.Zastanawiałem się nad użyciem
Lazy<T>
właściwości do poprawy wydajności własnego kodu (i aby dowiedzieć się więcej na ten temat). Przyszedłem tutaj, szukając odpowiedzi na temat tego, kiedy go użyć, ale wygląda na to, że wszędzie, gdzie idę, są takie frazy:z MSDN Lazy <T> klasy
Jestem trochę zdezorientowany, ponieważ nie jestem pewien, gdzie narysować linię. Na przykład interpolację liniową traktuję jako dość szybkie obliczenie, ale jeśli nie muszę tego robić, to czy leniwa inicjalizacja pomoże mi tego uniknąć i czy warto?
W końcu postanowiłem wypróbować własny test i pomyślałem, że podzielę się tutaj wynikami. Niestety tak naprawdę nie jestem ekspertem w przeprowadzaniu tego rodzaju testów, dlatego cieszę się z komentarzy, które sugerują ulepszenia.
Opis
W moim przypadku szczególnie zainteresowałem się tym, czy Lazy Properties może pomóc w ulepszeniu części mojego kodu, która wykonuje wiele interpolacji (większość jest nieużywana), dlatego stworzyłem test porównujący 3 podejścia.
Stworzyłem osobną klasę testową z 20 właściwościami testowymi (nazwijmy je właściwościami t) dla każdego podejścia.
Wyniki testu są mierzone w ms i są średnią z 50 instancji lub 20 otrzymanych właściwości. Każdy test był następnie przeprowadzany 5 razy.
Wyniki testu 1: Instancja (średnio 50 instancji)
Wyniki testu 2: Pierwsze pobranie (średnio 20 nieruchomości dostaje)
Wyniki testu 3: Second Get (średnio 20 nieruchomości dostaje)
Spostrzeżenia
GetInterp
jest najszybszy do utworzenia zgodnie z oczekiwaniami, ponieważ nic nie robi.InitLazy
jest szybsze do tworzenia instancji niżInitInterp
sugerowanie, że narzut związany z konfigurowaniem leniwych właściwości jest szybszy niż moje obliczenia interpolacji liniowej. Jestem jednak trochę zdezorientowany, ponieważInitInterp
powinienem wykonywać 20 interpolacji liniowych (aby ustawić właściwości t), ale utworzenie instancji (test 1) zajmuje tylko 0,09 ms, podczas gdyGetInterp
wykonanie tylko jednej interpolacji liniowej zajmuje 0,28 ms za pierwszym razem (test 2) i 0,1 ms, aby zrobić to za drugim razem (test 3).Zdobycie właściwości za pierwszym razem zajmuje
InitLazy
prawie 2 razyGetInterp
, podczas gdyInitInterp
jest najszybsze, ponieważ wypełniło swoje właściwości podczas tworzenia wystąpienia. (Przynajmniej tak powinno być, ale dlaczego wynik tworzenia instancji był o wiele szybszy niż pojedyncza interpolacja liniowa? Kiedy dokładnie wykonuje te interpolacje?)Niestety wygląda na to, że w moich testach dzieje się automatyczna optymalizacja kodu.
GetInterp
Zdobycie właściwości za pierwszym razem powinno zająć tyle samo czasu, co za drugim razem, ale pokazuje się ponad dwukrotnie szybciej. Wygląda na to, że ta optymalizacja wpływa również na inne klasy, ponieważ wszystkie zajmują tyle samo czasu na test 3. Jednak takie optymalizacje mogą również mieć miejsce w moim własnym kodzie produkcyjnym, co również może być ważnym czynnikiem.Wnioski
Chociaż niektóre wyniki są zgodne z oczekiwaniami, istnieją również bardzo interesujące nieoczekiwane wyniki, prawdopodobnie z powodu optymalizacji kodu. Nawet w przypadku klas, które wyglądają, jakby wykonały dużo pracy w konstruktorze, wyniki tworzenia instancji pokazują, że ich tworzenie może być bardzo szybkie, w porównaniu do uzyskania podwójnej właściwości. Podczas gdy eksperci w tej dziedzinie mogą być w stanie komentować i badać dokładniej, moim osobistym odczuciem jest to, że muszę ponownie wykonać ten test, ale na moim kodzie produkcyjnym, aby sprawdzić, jakie rodzaje optymalizacji mogą tam mieć miejsce. Spodziewam się jednak, że
InitInterp
może to być właściwa droga.źródło
lazy
wymaga dodatkowej księgowości,InitLazy
zużyłby więcej pamięci niż inne rozwiązania. Może również mieć niewielki spadek wydajności przy każdym dostępie, podczas gdy sprawdza, czy ma już wartość, czy nie; sprytne sztuczki mogłyby usunąć to obciążenie, ale wymagałoby to specjalnego wsparcia w IL. (Haskell robi to przez wywołanie każdej leniwej wartości wywołaniem funkcji; po wygenerowaniu wartości jest ona zastępowana funkcją, która zwraca tę wartość za każdym razem.)Wystarczy wskazać na przykład opublikowany przez Mathew
zanim urodziliby się Lazy, zrobilibyśmy to w ten sposób:
źródło
Z MSDN:
Oprócz odpowiedzi Jamesa Michaela Hare'a, Lazy zapewnia bezpieczną dla wątków inicjalizację twojej wartości. Spójrz na pozycję MSDN wyliczenia LazyThreadSafetyMode opisującą różne typy trybów bezpieczeństwa wątków dla tej klasy.
źródło
Powinieneś spojrzeć na ten przykład, aby zrozumieć architekturę Lazy Loading
-> wyjście -> 0 1 2
ale jeśli ten kod nie pisze „list.Value.Add (0);”
wynik -> Wartość nie została utworzona
źródło