niedziela, 6 maja 2012

Porównanie IoC

To jest mój pierwszy post, ale od czegoś trzeba zacząć. Wybacz więc niedociągnięcia :).

Na pierwszy temat rzucam porównanie wydajności kilku bibliotek (framework'ów) do wstrzykiwania zależności (Dependency Injection), które jest częścią paradygmatu odwróconego sterowania (Inversion of Control) - więcej można przeczytać tutaj.

Ostatnio przeczytałem, że znaczna większość programistów korzysta z tych dobrodziejstw. Niby dobrze, o ile wiemy z czym mamy do czynienia. Część bibliotek jest przeładowana tak dużą ilością funkcjonalności, z których przeciętny programista nigdy nie skorzysta, więc czy jest sens używania danej biblioteki, skoro daje ona duży narzut czasowy na wykonanie?

Właśnie dlatego powstał ten post. Przetestowałem kilka bibliotek, tych mniejszych i większych, które potrafią robić to samo, czyli tworzyć obiekty bazując na interfejsach. Niby każda robi to samo, ale część w znacznie inny sposób, co odbija się na wydajności danego rozwiązania. O ile dana funkcjonalność wykorzystywana jest w naszych rozwiązaniach rzadko, wydajność może nie ma tak dużego znaczenia. Problem zaczyna się wtedy, gdy "walczymy" o każde milisekundy w wykonaniu naszego kodu.

Do testów przygotowałem 2 interfejsy po 2 implementacje, z czego ILogger korzysta z obiektu IObject.


W testach wykorzystałem jedynie implementację FileLogger oraz SmallObject.

Do testów wykorzystałem następujące "silniki" potrafiące wstrzykiwać zależności:

  • Własny
  • SimpleInjector
  • Spring.NET
  • CastleWindsor
  • StructureMap
  • Unity
  • Autofac
  • Ninject
Wykonałem 21 prób. Każda próba tworzyła lub odczytywała konfigurację z pliku i 10000 razy próbowała utworzyć obiekt dziedziczący z interfejsu ILogger (pamiętając, iż konstruktor ILogger wymaga obiektu dziedziczącego po interfejsie IObject).



Jak widać część z bibliotek radzi sobie nadzwyczaj dobrze z "rozpoznawaniem" interfejsów i tworzeniem odpowiednich obiektów, część po prostu sobie z tym nie radzi przy takim obciążeniu. Pierwsza próba jest zawsze wolniejsza, a jest to spowodowane inicjacją tychże bibliotek oraz zaczytaniem konfiguracji.

Odrzucając 3 ostatnie (najbardziej powolne) biblioteki, wykonałem powtórkę testu, a poniżej przedstawiam wyniki.


Wnioski są takie:

  1. Jeżeli chcesz użyć bardzo wydajnej biblioteki, prostej, lekkiej, użyj własnego rozwiązania lub bibliotek SimpleInjector, która w moim przypadku okazała się być szybsza niż własne rozwiązanie :)
  2. Jeżeli chcesz użyć prawie tak samo wydajnej biblioteki jak w poprzednim przypadku, ale zamierzasz użyć różnych dobrodziejstw w nich umieszczonych, to skorzystaj ze Spring.NET lub CastleWindsor.
  3. Jeżeli chcesz używać czegokolwiek nie zwracając uwagi na wydajność, możesz użyć pozostałych bibliotek, takich jak StructureMap, Unity, Autofac czy Ninject (dwa ostatnie dają bardzo duży narzut czasowy, więc w ogóle zastanawiałbym się czy kiedykolwiek je stosować).
W powyższym teście nie brałem pod uwagi wielu czynników, takich jak:
  • czasu tworzenia bardzo skomplikowanych obiektów w porównaniu do ich prostych odpowiedników (zastosowany przykład 2 interfejsów zależnych można zaliczyć do kategorii średnio-prostych)
  • czasu tworzenia lub zaczytywania konfiguracji w zależności od ilości interfejsów do obsłużenia (ilość danych w słownikach nie powinna bardzo pogarszać wydajności, ale różne są implementacje i różne mogą dać wyniki)
  • ilość interfejsów i obiektów dostępnych do faktoryzacji, co może mieć znaczenie przy czasie rozwiązywania nazw
  • prostoty tworzenia konfiguracji (w kodzie czy w osobnym pliku konfiguracyjnym)
  • możliwości funkcyjnych bibliotek do dalszego rozwoju kodu

Test ten jest bardzo poglądowy i ma na celu uświadomienie jak ważny jest dobór odpowiednich narzędzi programistycznych, z których później korzystamy na co dzień. Z jednej strony możesz użyć 1% funkcjonalności z bardzo rozbudowanej biblioteki i nie przejmować się wydajnością, z drugiej strony możesz zastanowić się czy nie użyć najprostszej formy odwróconej kontroli ze względu na wydajność i konieczność użycia tylko tej wyłącznie funkcjonalności, która w danym przypadku może wystarczyć, a być jednocześnie krytyczna ze względu na wydajność oprogramowania, a może użyjesz czegoś pośredniego.

Decyzję do wyboru odpowiedniego narzędzia zostawiam Tobie.

20 komentarzy:

  1. Dorzucę 3 uwagi:
    1) nie zalecałbym NIGDY stosowania własnego rozwiązania do IoC, niezależnie od jakichkolwiek testów wydajności; chyba że mówimy tylko i wyłącznie o zabawie w projekcie, który za tydzień na 100% pójdzie do kosza
    2) wydajność w kontekście IoC nie ma moim zdaniem znaczenia przy jakimkolwiek projekcie; jedno najprostsze odwołanie do bazy danych prawdopodobnie zajmie tyle czasu co działanie dowolnego IoC przez miesiąc
    3) nie pokazałeś żadnego kodu: w jaki sposób testowałeś wydajność tych bibliotek? domyślam się, że wykonywałeś coś w stylu "ioc.Resolve()", a należy pamiętać że w kodzie aplikacji raczej nieczęsto taki kod sami umieścimy

    OdpowiedzUsuń
  2. Procent:

    Ad.1. Dlaczego nie polecasz własnego rozwiązania? Myślisz, że było by o wiele gorsze niż istniejące solucje dostępne "na rynku"? Jeżeli potrzebowałbym bardzo, ale to bardzo prostego IoC, który mógłbym dowolnie rozszerzać na swoje potrzeby, i to używać produkcyjnie, to dlaczego by nie?

    Ad.2. Nie do końca się zgodzę. Możliwe, że w większości projektów tak jest, iż narzut nie jest tak duży. Tak jak pisałem w poście, są sytuacje, kiedy "walczymy" o każdą milisekundę, a wtedy wybór odpowiedniego IoC może mieć ogromne znaczenie. Wiele razy spotykałem się z podejściem programistów, iż dane rozwiązanie nie ma negatywnego wpływu na wydajność, a nawet jeśli ma to mały (wręcz nieistotny); po czym po pół roku później okazuje się, że wydajność tak spada, że zaczynamy mieć problemy na produkcji, które czasami cholernie trudno jest zreplikować na wewnętrznych środowiskach w celu analizy i poprawy. Poza tym post ten powstał na skutek projektu nowej funkcjonalności, która nie korzysta z bazy danych, ale korzysta z IoC, a zarazem które ma wpływ na wydajność *całej* aplikacji i wszystkich korzystających z niej użytkowników; czyli musi być mega wydajne!

    Ad.3. Ok, moja wina, moja bardzo wielka wina :). Nie chcę jednak "mieszać" już w tym poście, więc napiszę część drugą tego postu już z opisem kodu, konfiguracji poszczególnego rozwiązania. Może rozwinę temat o te aspekty, których nie rozwinąłem w tym tekście.

    Pozdrawiam

    OdpowiedzUsuń
  3. Sławomir,

    Nie polecałbym własnego rozwiązania ani jeśli chodzi o IoC, ani jeśli chodzi o loggera, ani jeśli chodzi o dostęp do danych, ani......
    Po prostu jestem wielkim przeciwnikiem "syndromu NIH" i zbyt wiele razy musiałem babrać się w kupie zostawionej przez kogoś, kto postanowił że dostępne projekty, wykorzystywane przez tysiące osób, obecne na rynku od lat, utrzymywane przez całe zespoły programistów, są gorsze niż jego własny, nowy. Z jakichkolwiek powodów.
    Jeśli programista twierdzi że faktycznie potrzeby jego projektu są tak bardzo rozbieżne od wszystkich innych powstających na świecie systemów, że musi pisać własną bibliotekę do IoC, to prawdopodobnie coś robi nie do końca tak jak trzeba:).

    Jeśli chodzi o wydajność - ok, zgadzam się, w 0.0001% przypadków wydajność IoC będzie miała znaczenie. Tylko czy wtedy, gdy naprawdę walczymy o ułamki sekund, warto stosować IoC?

    OdpowiedzUsuń
  4. Procent:

    Gdyby tak miało być to nie powstałyby inne frameworki IoC, które zostały zapoczątkowane jako projekty aż stały się pełnoprawnymi frameworkami. Przykładowo Nijnect, który powstał stosunkowo późno jako pierwszy wprowadził Context Based DI, gdyby jego twórca stosował się do dewizy "NIH" takie rozwiązanie prawdopodobnie nigdy by nie powstało :)

    Natomiast nowe rozwiązania/frameworki powinny być implementowane dopiero wtedy kiedy dla naszego przypadku jest to absolutna konieczność ( brak feature, niespójność architektury prowadząca do wyjątków ), więc pod tym względem zgadzam się z Tobą ale nie negował bym całkowicie takiego rozwiązania.

    Sławomir:

    Jeśli idzie o wydajność IoC to dobranie odpowiedniego powinno być dość proste gdyż ich wydajność zazwyczaj jest stała w czasie. Natomiast jeśli mówimy o wydajności IoC to o ile nie tworzymy kilku ( dziesięciu ) tysięcy obiektów na sek, to ich narzut może być całkowicie pomijalny. Zwyczajowo narzut, które generują może być odzyskany po przez pisanie wydajniejszej logiki oraz algorytmów, tam właśnie szukał bym największych oszczędności.

    Przykładowo, szukanie elementu w Liście napisane w taki sposób:

    var results = from x in tab
    where (x >= 10)
    select (x * x);
    return results.ToArray();

    może byc od kilku do kilkunastu razy szybsze gdybyśmy użyli klasycznego algorytmu z pętlą for do przeszukiwania porządanego elementu. Jeśli przyjąć że taki kod odpalałby się kilka razy na sek dla tablicy < 1000 elementów oszczędności *czasowe* jak i *pamięciowe* byłyby Bardzo znaczne. Uważam więc że najpierw należy szukać oszczędności u siebie następnie brać się za wyniamne/przepisanie frameworków (o ile ich kod nie jest rażąco zły, co bardzo żadko ma miejsce ale zdarza się).

    OdpowiedzUsuń
  5. Witam,

    Czy możesz udostępnić projekt benchmarka, którym to testowałeś?

    OdpowiedzUsuń
    Odpowiedzi
    1. Michał,
      Jasne - wyczyszczę solucję i udostępnię :)

      Usuń
  6. grindcode,
    To post sprzed półtora roku, więc dzisiaj informacje z niego mogą być nieprawdziwe i wprowadzające w błąd.

    OdpowiedzUsuń
  7. Panowie, jestem pod wrażeniem ilości wolnego czasu, którym dysponujecie. Jesteście bezrobotni?

    OdpowiedzUsuń
    Odpowiedzi
    1. mszafranski,
      Co wnosi Twój komentarz? Nic.

      Usuń
    2. Jednak nic się nie zmieniłeś. W Millennium też mnie tak traktowałeś :(

      Usuń
    3. A ja ci tyle kasy pożyczyłem...

      Usuń
    4. Będziesz udawał, że nic się nie stało?

      Usuń
    5. Z tym czyszczeniem i udostępnianiem solucji to też była ściema? Obiecałeś przecież...

      Usuń
    6. Jak długo będziesz jeszcze solucję czyścił? Kulałeś ją w błocie? Wlokłeś na wielbładzie przez kompostownik? Bardzo jeszcze brudna jest? Obiecałeś, że zamieścisz oczyszczoną solucję i co?
      Czy traktujesz to jako żart? Jednorazowy blogging, chwilowa rozrywka? Co z nami? Z audytorium przyciągniętym chwytliwym nagłówkiem IoC? Co teraz?
      Co zamierzasz teraz? Mnie pewnie zignorujesz, jak zwykle, jesteś taki niewrażliwy, ale z resztą ludków? Ich też będziesz tak traktował?

      Usuń
    7. Ty zimny, nieczuły draniu...

      Usuń
  8. Jak na pierwszy post to obrałeś bardzo ciekawy temat. Podejście trochę nieoczywiste bo raczej spodziewał bym się porównania tych narzędzi pod względem ich możliwości, a nie czasu tworzenia obiektów, ale wyszło całkiem interesująco. Przez samego posta jak i przez komentarze przebija jeszcze pewna taka naiwnośc ale można Wam to jeszcze wybaczyc z racji Waszego niedoświadczenia i młodego wieku, jednak bardzo uśmialiśmy się tu w Google'u z Waszej napinki :) Droga młodzieży, w dużych aplikacjach chodzi przede wszystkim o skalowalnośc. Skalowalnośc, skalowalnośc i jeszcze raz skalowalnośc. Z tego punktu widzenia istotniejsze niż czas tworzenia obiektu są możliwości zarządzania scopem grafu obiektów. Przy zakupach serwerów za 80 000 EURO, nie ma znaczenia czy trzeba wydac jeszcze pare tysiakow na maszynke amortyzujaca niech by nawet kilkumilisekundową obsuwkę resolwowania grafu obiektów. I tak wyjdzie dużo taniej niż opłacanie ślęczącego człowieka. Jako temat następnego posta proponuję analizę zachowania i możliwości kontrolowania zagnieżdżonych scope'ów. Jakie są mocne strony, jakie są przeciwskazania itd. Mimo wszystko miło patrzec, że młodzież stara się zają czymś innym niż alkohol, narkotyki i popełnianie przestępstw. Jeśli jesteście zainteresowani bezpłatnym stażem w naszej firmie i macie już ukończone szkoły prześlijcie swoje eng CV z dopiskiem "Borowski". Resztą się zajmę. Pozdrowienia młodzieży.

    OdpowiedzUsuń
  9. Witaj!

    Wydaje mi się, że tytuł posta sugeruje coś innego niż treść. Spodziewałem się raczej dużego porównania możliwości tych narzędzi, a nie tylko sprawdzenia, który framework tworzy szybciej obiekty. W przypadku tego typu narzędzi, różnice na poziomie milisekund nie mają znaczenia, i nie słyszałem jeszcze, żeby ktoś podjął decyzję o wyborze innego frameworka z tego powodu. Zachęcam Cię do pociągnięcia tematu dalej i przygotowaniu trochę bardziej treściwego porówniania. Jak na pierwszy post jest całkiem nieźle, czekam na kolejne. Pochwal się jeszcze ile lat zajmujesz się programowaniem i jak długo pracujesz z frameworkami IoC ?

    OdpowiedzUsuń
  10. Ten komentarz został usunięty przez autora.

    OdpowiedzUsuń
  11. Sławku, z niecierpliwością czekam na oczyszczoną solucję i nowe wpisy. Pozdrawiam.

    OdpowiedzUsuń