Porównanie optymalizatorów PHP – eAccelerator, PHP APC, XCache

Przez pewien czas korzystałem z eAcceleratora do przyspieszenia działania stron pisanych w PHP’ie ale czasem bywał niestabilny. Aktualizacje pojawiały się rzadko a od czasu do czasu miewałem problemy ze stabilnością tej wtyczki na kilku bardziej skomplikowanych aplikacjach. Zdarzało się że pomimo zmiany kodu w skrypcie php, eAccelerator serwował wciąż stary plik – konieczny był restart Apache’go by wszystko działało jak trzeba.

Zacząłem szukać alternatywy i trafiłem na dwa moduły:

  • APC (czyli Alternative PHP Cache), który ma być nawet domyślnie wbudowany w PHP od wersji 5.4,
  • XCache – projekt rozwijany przez jednego z programistów lighttpd.

Byłem ciekaw wydajności poszczególnych rozwiązań względem siebie, więc przygotowałem małe środowisko testowe składające się z 4 maszyn wirtualnych (działających pod kontrolą VirtualBox’a):

  1. Apache 2.2 + PHP 5.3
  2. Apache 2.2 + PHP 5.3 + eAccelerator 0.9.6.1 (instrukcja instalacji tutaj)
  3. Apache 2.2 + PHP 5.3 + APC 3.1.3p1 (pod Debianem paczka php-apc)
  4. Apache 2.2 + PHP 5.3 + XCache 1.3.0 (pod Debianem paczka php5-xcache)

Najpierw przygotowałem pierwszą maszynę wraz z konfiguracją MySQL’a i domyślną instalacją WordPress’a 3.2 by test był w miarę miarodajny. Kolejne maszynki to klony tej pierwszej, plus zainstalowane i domyślnie skonfigurowane kolejne rozszerzenia. Każdemu z rozszerzeń przyznałem 32 MB pamięci na cache.

Metodyka testu

Do automatyzacji testu posłużył mi skrypt w bash’u uruchamiający ab dla 1000 zapytań z kolejno rosnącą liczbą równoległych połączeń. Pozwoli to na porównanie wydajności optymalizatorów przy mniejszym i większym obciążeniu. Uruchomienie testu dla czystej instalacji bez dodatku pokaże jak duży przyrost wydajności daje się uzyskać.

Jako systemy testowe posłużyły mi wirtualki uruchomione pod kontrolą VirtualBOX’a z zainstalowanym aktualnym Debianem Squeeze. Przydzieliłem im po 1 GB RAM’u i 2 rdzenie CPU. Wirtualki te raczej nie są highend’em ale do ogólnego porównania optymalizatorów będą w zupełności wystarczające.

Wyniki

Już na pierwszy rzut oka widać że opłaca się zainstalować dowolny optymalizator bo ich wydajność jest zbliżona a w stosunku do czystej instalacji pozwalają obsłużyć prawie czterokrotnie większy ruch. Przy czym system bez opcode cacher’a nie pokonał granicy 70 zapytań na sekundę – zaczął swapować i nie ukończył kolejnych testów.

Poniżej wykres przedstawiający ilość obsługiwanych żądań w zależności od ilości równoczesnych połączeń:
Benchmark optymalizatorów PHP - wydajność w zapytaniach/sekundę
I jeszcze jeden wykres, na którym porównałem wydajność poszczególnych optymalizatorów względem czystego PHP’a
Benchmark optymalizatorów PHP - wydajność względem czystego PHP

Wychodzi na to że przez większość testu eAccelerator był najszybszy, gdzieniegdzie przeplatając się z APC. XCache nieznacznie ale na całej długości poniżej dwóch wcześniejszych. Całościowe różnice pomiędzy optymalizatorami przeważnie nie przekraczały 3 zapytań/sekundę – więc różnice pomiędzy nimi są rzędu 1~2%. Można na tej podstawie wywnioskować że wydajność jest tak zbliżona iż nie powinna być jedynym kryterium wyboru optymalizatora dla naszego systemu.

Poniżej postaram się zebrać subiektywne oceny poszczególnych rozwiązań by dostarczyć dodatkowych argumentów.

eAccelerator

eAccelerator był najszybszy w teście ale miewałem z nim problemy (kilka razy ale…) stąd nie jest moim faworytem.

Zalety:

  • zdecydowanie najszybszy,
  • jest stosunkowo aktywnie rozwijany,
  • dość stabilny,
  • wbudowany encoder i dekoder skryptów (do dystrybucji kodu bez źródeł w postaci skompilowanej).

Wady:

  • brak paczek w repozytoriach Debiana – ręczna kompilacja nie jest ciężka ale gdy trzeba go utrzymać na 30 serwerach to przestaje być zabawnie,
  • pomimo że pojawiają się nowe wersje to ostatnio miałem problemy z pobraniem ich ze strony projektu – szukanie paczek “gdzieś” na sieci nie wydaje mi się bezpieczne,
  • miałem problem z pewną dużą aplikacją, nie działała stabilnie pod eAcceleratorem,
  • eAccelerator powstał na bazie kodu Turck MMCache (ten nie jest już rozwijany) -istnieją pewne wątpliwości licencyjne co do jego kodu…

PHP APC

APC pod względem wydajności nieznacznie ustępuje eAcceleratorowi. Ciekawą funkcją udostępnianą przez APC jest obsługa RFC1867 (File Upload Progress hook handler). Jest też pewna potwierdzona plotka mówiąca o włączeniu kodu APC do PHP’a 6. Teoretycznie jeżeli przesiądziemy się już teraz na APC to później powinno pójść łatwiej…

Zalety:

  • dość szybki,
  • aktywnie rozwijany,
  • bardzo stabilny,
  • dostępna paczka w repozytoriach Debiana (i z tego co wiem na wielu innych systemach też przeważnie wystarczają domyślne repozytoria)
  • obsługa RFC1867 (upload progress),
  • zostanie włączony do PHP od wersji 5.4,
  • APC udostępnia API umożliwiające tworzenie własnych obiektów w pamięci cache współdzielonych pomiędzy zapytaniami np. by nie pobierać za każdym razem właściwości profilu z bazy, listy użytkowników, itp (coś w stylu memcached).
  • dostępny jest ze skryptem apc.php, który pozwala zarządzać obiektami w cache, czyścić itp.

Wady:

  • podobno bywa problematyczny w konfiguracji z fast-cgi (choć u mnie działa),
  • przy mocno zapchanym cache’u czyszczenie go potrafiło się zwiesić.

XCache

Ostatni projekt rozwijany jest przez jednego z programistów lighttpd.  W chwili obecnej wydaje się być dość dojrzałym i wystarczająco stabilnym do produkcyjnego użycia. Choć gdy próbowałem z niego korzystać jakiś rok/dwa temu to miałem sporo losowych padów – niezależnych od obciążenia serwera.

Zalety:

  • stabilny,
  • aktywnie rozwijany z kilkoma gałęziami (stable/unstable/devel) – możemy wybrać czy potrzebujemy funkcji czy stabilności.

Wady:

  • nieznacznie, ale jednak najniższa wydajność,
  • miałem z nim mało styczności a szybko zraziłem się do kiepskiej stabilności – obecnie wydaje się że nie stanowi to problemu.

Podsumowanie i mój wybór

Do testu celowo wybrałem WordPress’a jako dość duży i wystarczająco skomplikowany projekt – jeśli on będzie działać stabilnie to większość mniejszych też powinna… Ku mojemu zaskoczeniu żaden z optymalizatorów nie sypnął błędami. Dziwiło mnie to bo jeszcze jakiś czas temu eAccelerator czasami losowo mi się sypał – działał przez tydzień i nagle zgon w sobotę po południu… Później próbowałem XCache i było podobnie… tylko gorzej bo problemy występowały częściej. APC testowałem jako ostatnie ale w wykorzystywanych przeze mnie aplikacjach zachowywał się bardzo stabilnie i przewidywalni. Jedyny problem z wieszaniem się podczas czyszczenia/usuwania elementu z cache’u można obejść stosunkowo szybkim restartem Apachego – skuteczne i efekt ten sam 🙂 Na jednym z serwerów testuję APC w trybie fast-cgi od około dwóch miesięcy i jak na razie nie mogę narzekać (może w wolnej chwili uzupełnię to zestawienie o testy w trybie fast-cgi).

Obecnie w większości administrowanych przeze mnie serwerów z PHP’em standardowo instaluję APC. Wybór jest dla mnie tym bardziej oczywisty że paczka ta jest dostępna w standardowych repozytoriach (łatwość aktualizacji itd) – nie ma zatem potrzeby jak w przypadku eAcceleratora instalowania wielu paczek z zależnościami by móc skompilować 1 moduł.

Dodatkową zaletą jest fakt że APC ma być standardowo wbudowany w kolejne wersje PHP’a – jeżeli w rozwijanych aplikacjach już teraz zwróci się uwagę na integrację z tym rozwiązaniem to w przyszłości migracja nie powinna przysporzyć problemów.

Jeżeli się wahasz – wybierz APC. Jeżeli w Twoim środowisku okaże się niestabilne zawsze możesz spróbować dwóch pozostałych rozwiązań.

Przydatne linki (część potwierdza moje obserwacje, są też testy z drupalem):
http://php.net/manual/en/book.apc.php
http://xcache.lighttpd.net/
http://eaccelerator.net/ (w chwili pisanie strona nie działała 🙁 )
http://www.ducea.com/2006/10/30/php-accelerators/
http://2bits.com/articles/benchmarking-drupal-with-php-op-code-caches-apc-eaccelerator-and-xcache-compared.html
http://2bits.com/articles/benchmarking-apc-vs-eaccelerator-using-drupal.html
http://hostingfu.com/article/increasing-php-application-performance-xcache

Optymalizacja PHP z eAccelerator’em

Przy okazji wykonywania kilku drobnych optymalizacji swojej stronki natknąłem się na eAccelerator’a. Ciekawy projekt, który w sposobie działania przypomina Zend Optimizer’a ale ma jedną zasadniczą zaletę – jest darmowy 🙂

Niestety nie ma go w repozytoriach Debiana, więc trzeba go sobie skompilować – cały proces jest dość prosty. Zaczynamy od pobrania najświeższej paczki, obecnie jest to wersja 0.9.5.3:

Pobierz eAccelerator (ostatnio miałem problem z tym linkiem więc proponuję pogooglać)

Pobieramy i rozpakowujemy pliki:

tar xvfj eaccelerator-0.9.5.3.tar.bz2
cd eaccelerator-0.9.5.3

Do kompilacji eAccelerator’a potrzebujemy paru paczek, które możemy zainstalować tak:

apt-get install build-essential php5-dev

W katalogu ze źródłami klepiemy (prawie standardowo):

phpize
./configure
make
make install

No i eAccelerator jest już zainstalowany w naszym systemie. Pozostało wygenerowanie konfiguracji PHP aby moduł był automatycznie ładowany oraz ustawienie podstawowych parametrów konfiguracyjnych.
W przypadku Debiana domyślna konfiguracja modułów PHP przechowywana jest w katalogu/etc/php5/conf.d tam też zapiszemy naszą konfigurację jako eaccelerator.ini. Tworzymy plik naszym ulubionym edytorem:

vim /etc/php5/conf.d/eaccelerator.ini

W pliku wpisujemy:

extension="eaccelerator.so"
eaccelerator.shm_size="128"
eaccelerator.cache_dir="/tmp/eaccelerator"
eaccelerator.enable="1"
eaccelerator.optimizer="1"
eaccelerator.check_mtime="1"
eaccelerator.debug="0"
eaccelerator.filter=""
eaccelerator.shm_max="0"
eaccelerator.shm_ttl="0"
eaccelerator.shm_prune_period="0"
eaccelerator.shm_only="0"
eaccelerator.compress="1"
eaccelerator.compress_level="9"

extension – określa plik modułu, czasami może być konieczne podanie pełnej ścieżki,
eaccelerator.shm_size – określa ilość pamięci współdzielonej, którą eAccelerator rezerwuje do przechowywani skompilowanych skryptów. Ja dałem 128MB bo mój serwerek ma 2GB RAM-u, ale przypuszczam że dla mniej obciążonych maszyn wystarczy 16~32MB. Nadając temu parametrowi wartość 0 ufamy programistom 😉
eaccelerator.cache_dir – ustawiamy katalog, w którym będzie zapisywane to co nie będzie się już mieścić w pamięci współdzielonej. Ja mam akurat sporą osobną partycję na /tmp, ale równie dobrze nadaje się /var/cache/eaccelerator czy /var/tmp/eaccelerator – ustawiamy jak nam wygodnie,

Skoro już piszę o katalogu na skrypty to warto go utworzyć i przypisać mu odpowiednie uprawnienia (ja ustawiam je tak aby tylko Apache miał dostęp):

mkdir /tmp/eaccelerator
chown -R www-data:www-data /tmp/eaccelerator
chmod 0770 /tmp/eaccelerator

No to jeszcze restart Apache’a i możemy sprawdzać jak działa:

invoke-rc.d apache2 restart

Najszybszym sposobem sprawdzenia czy moduł się ładuje jest sprawdzenie wyniku poleceniaphp -v – powinno to wyglądać jak poniżej:

~$ php -v

Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies
    with eAccelerator v0.9.5.3, Copyright (c) 2004-2006 eAccelerator, \
    by eAccelerator

Podobny wynik można uzyskać z pomocą funkcji phpinfo() i prostego skryput PHP do jej wywołania.

Czy warto?

Na koniec trochę benchmarków aby sprawdzić czy cała zabawa jest warta świeczki 🙂
Wykorzystam standardowe narzędzie dostępne z Apache’m: ab (Apache benchmark):

ab -n 1000 -c 10 http://mojastrona.pl/

U mnie dało to następujące wyniki – z wyłączonym eAccelerator’em:

Concurrency Level:      10
Time taken for tests:   8.648 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      12836000 bytes
HTML transferred:       12612000 bytes
Requests per second:    115.64 [#/sec] (mean)
Time per request:       86.477 [ms] (mean)
Time per request:       8.648 [ms] (mean, across all concurrent requests)
Transfer rate:          1449.54 [Kbytes/sec] received

Oraz z włączonym:

Concurrency Level:      10
Time taken for tests:   3.663 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      12840344 bytes
HTML transferred:       12616120 bytes
Requests per second:    272.97 [#/sec] (mean)
Time per request:       36.634 [ms] (mean)
Time per request:       3.663 [ms] (mean, across all concurrent requests)
Transfer rate:          3422.90 [Kbytes/sec] received

Wynik jest całkiem niezły, ponad dwa razy szybciej. A w niektórych przypadkach udaje się wyciągnąć więcej (nawet 5~10 krotnie). Tak, czy siak – warto!