Blog
Auryn DI – analiza wydajności
Tagi: dependency injection, php, symfony
Niedawno natrafiłem na nowe dla mnie narzędzie typu Dependency Injection dla PHP – Auryn (https://github.com/rdlowrey/Auryn). W odróżnieniu od znanych mi do tej pory Pimple i Symfony DI, Auryn opiera się na refleksji. Autorzy zapewniają:
You may have heard that “reflection is slow”. Let’s clear something up: anything can be “slow” if you’re doing it wrong.
W tym samym czasie robiłem też przegląd narzędzi DI dla Android-a jak RoboGuice, Android Annotation, Dagger, gdzie pierwsze jest oparte o refleksję i powszechnie odradzane przez środowisko jako zbyt wolne. Postanowiłem zweryfikować zapewnienia autorów Auryn-a co do jego szybkości działania.
Czy refleksja jest wolna?
Zmierzyłem, że użycie ReflectionClass do sprawdzenia typów argumentów konstruktora danej klasy zajmuje 3-4 razy więcej czasu niż samo stworzenie instancji tej klasy. Jak twierdzą autorzy, jest to mimo wszystko, szybki proces, bo odbywa się w pamięci i nie wymaga dostępu do zewnętrznych zasobów, jak system plików czy baza danych. Symfony DI cache-uje skompilowany kontener w formie klasy, która ma zahardkodowane nazwy klas potrzebnych do zbudowania wybranych obiektów (serwisów). W związku z tym, że oba narzędzia muszą utworzyć tyle samo obiektów, a Auryn dodatkowo, aby utworzyć pojedynczy obiekt musi zbadać argumenty jego konstruktora, co zajmie mu 3-4 razy tyle czasu co utworzenie instancji, to spodziewam się 4-5 krotnej przewagi szybkości Symfony DI nad Auryn.
Cache-owanie obiektów refleksji
Autorzy Auryn dostarczają klasę ApcReflectionPool, która powinna zapewnić zwiększenie wydajności poprzez użycie APC cache do przechowywania obiektów refleksji. Jak twierdzą autorzy wzrost wydajności wynosi 20-30%. Niestety od PHP 5.5 “APC user cache” nie jest dostępny, zachowano jedynie samą akcelerację (generowanie OPCode) z tym, że jest ona emulowana przez rozszerzenie OPcache. Część odpowiedzialna za cache użytkownika została wyciągnięta do osobnego rozszerzenia APCu. Zainstalowałem to rozszerzenie, niestety przy próbie odczytu z cache zwracany jest błąd:
( ! ) Fatal error: ReflectionClass::getParentClass(): Internal error: Failed to retrieve the reflection object
Nie zrażając się, postanowiłem napisać prostą klasę rozszerzającą Auryn/ReflectionPool, realizującą cachowanie w oparciu o Memcache. Nie zajęło to dużo czasu i… niestety nie działało :). Okazuje się że cache-owanie obiektu ReflectionClass nie jest takie proste: jest to klasa wbudowana i operacja jej serializacji jest stratna. Otrzymywałem identyczny komunikat, jak w przypadku użycia ApcReflectionPool + APCu. Po przejrzeniu kodu źródłowego stwierdziłem, że core-owa klasa ReflectionPool zakłada, że cache-owany jest obiekt ReflectionClass, który jak pisałem nie nadaje się do standardowej serializacji. Bez dużych przeróbek w ReflectionPool, mających na celu umożliwienie cache-owania wyników refleksji (które można zserializować), a nie całego obiektu refleksji, nie widzę szans na otwarcie biblioteki na inne mechanizmy cache.
Po tych nieudanych próbach uruchomienia cache, postanowiłem przejść do testu porównawczego.
Benchmark
Do przeprowadzenia porównania wydajności generowałem:
- hierarchię kompozycji klas
class Class1 { function __construct(Class2 $dependency) { $this->obj = $dependency; } } class Class2 { function __construct(Class3 $dependency) { $this->obj = $dependency; } }
- hierarchię kompozycji serwisów (Symfony DI)
services: service_1: class: Class1 arguments: - @service_2 service_2: class: Class2 arguments: - @service_3
Oba narzędzia Dependency Injection miały za zadanie stworzyć instancję pierwszej klasy z wygenerowanej kompozcji. Wartości, które postanowiłem zmierzyć to czas utworzenia obiektu oraz zużycie pamięci podczas tego procesu. Wartość sterowana to liczba zagnieżdżeń w kompozycji klas.
Podsumowanie wyników
Wyniki potwierdziły moje wcześniejsze przewidywania. Jak widać Auryn tworzy obiekty o 4-5 razy wolniej niż Symfony. Być może dodanie warstwy cache przed odwołaniem się do refleksji diametralnie zmieniłoby wyniki, niestety autorzy nie dostarczyli w bibliotece implementacji działającej z PHP >5.4.