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:
Użyj opóźnionej inicjalizacji, aby odroczyć utworzenie dużego lub zasobochłonnego obiektu, lub wykonanie zadania wymagającego dużej ilości zasobów, zwłaszcza gdy takie tworzenie lub wykonanie może nie nastąpić w trakcie życia programu.
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.
- Klasa GetInterp: Wykonuje interpolację liniową za każdym razem, gdy otrzymywana jest właściwość t.
- InitInterp Class: Inicjuje właściwości t, uruchamiając interpolację liniową dla każdej z konstruktorów. Get po prostu zwraca podwójne.
- InitLazy Class: Ustawia właściwości t jako właściwości Lazy, dzięki czemu interpolacja liniowa jest uruchamiana raz, gdy właściwość jest pobierana po raz pierwszy. Kolejne pobrania powinny po prostu zwrócić już obliczone podwójne.
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)
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
Wyniki testu 2: Pierwsze pobranie (średnio 20 nieruchomości dostaje)
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
Wyniki testu 3: Second Get (średnio 20 nieruchomości dostaje)
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
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 gdy GetInterp
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 razy GetInterp
, podczas gdy InitInterp
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.
get { if (foo == null) foo = new Foo(); return foo; }
. I jest zillions możliwych miejsc, aby z niego skorzystać ...