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.
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:
- 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 :)
- 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.
- 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.