Dziś jest piątek, 21 listopada 2008 roku (z kalendarza...)

Benchmark: Stałe w PHP

Icon

17.06.2008, 14:22

PHP

Komentarze (12)

Powrót

Obecnie w PHP dostępne są dwa rodzaje stałych: globalne oraz klasowe. Programiści coraz częściej wybierają te drugie, lecz ja dotąd pozostawałem nieco sceptyczny wobec zapewnień twórców o ich "większej wydajności", zwłaszcza że robione na szybko pomiary wskazywały mi coś zupełnie innego. Postanowiłem dzisiaj definitywnie sprawę rozstrzygnąć.

Przypomnę argumentację twórców i wielu programistów, dlaczego stałe klasowe mają być szybsze. Jak wiadomo, stałych globalnych jest dość dużo i są one zgromadzone w pamięci interpretera w jednej ogromnej strukturze danych. Przy każdym wywołaniu PHP musi zajrzeć do tej tablicy, odnaleźć stałą i pobrać jej wartość. W stałych klasowych proces odszukiwania podzielony jest na dwie części. Na początku odnajdujemy odpowiednią klasę, a później patrzymy w jej zbiorze stałych. Zbiór ten jest znacznie mniejszy, niż globalna pamięć, dlatego jego przeszukiwanie ma trwać krócej. Przekonajmy się, jak jest w istocie.

Wykonałem łącznie cztery testy, używając różnych technik liczenia czasu. W trzech z nich pomiar robiony był zwykłą funkcją microtime(), jeden został sprawdzony Apache Benchem. Zarówno pełne wyniki, jak i kod testów zamieszczam w załączniku. W testach 1, 3 i 4 najgorszy zmierzony rezultat był odrzucany.

Test 1

Jest to prosty test znaleziony na jakimś forum dyskusyjnym, gdzie autor zachwalał wydajność stałych klasowych i załączył stosowny kod demonstracyjny. Pomiar czasu robiony jest tutaj zwykłym microtime(), po czym zapisywany jest do pliku tekstowego. Skrypt miał zadeklarować dziesięć stałych i każdą z nich wyświetlić jedna, po drugiej. Pomiar powtarzano 25 razy. Wyniki uśrednione:

  1. Stałe globalne: t = 0,02612 s; u(t) = 0,00045
  2. Stałe klasowe: t = 0,01183 s; u(t) = 0,00011

Tutaj stałe klasowe faktycznie wypadają znacznie lepiej.

Test 2

Test mojego autorstwa. Skrypt miał zadeklarować 30 stałych, po czym wyświetlić 5 z nich 1000 razy. Tak duża ilość iteracji została wprowadzona pod kątem Apache Bencha, który bez tego działał bardziej jak generator liczb pseudolosowych, a nie wyników :). Parametry wywołania: -n 700 -c 100. Pomiar powtórzono 10 razy. Mierzony był średni czas przetworzenia żądania (w milisekundach):

  1. Stałe globalne: t = 1,5773 ms; u(t) = 0,0093 ms
  2. Stałe klasowe: t = 1,7456 ms; u(t) = 0,0080 ms

Niespodzianka, stałe globalne okazały się szybsze.

Test 3

Zaintrygowany wynikami testu numer 2, postanowiłem zmierzyć kod z testu 2 funkcją microtime(). Jedyna modyfikacja to usunięcie 1000-iteracyjnej pętli. Pomiary identycznie, jak w teście 1.

  1. Stałe globalne: t = 0,0485 s; u(t) = 0,0027 s
  2. Stałe klasowe: t = 0,01419 s; u(t) = 0,00017 s

Stałe klasowe znowu lepsze. Zacząłem już podejrzewać, o co tu chodzi. Dla pewności więc zrobiłem jeszcze jeden test...

Test 4

Jest to test numer 3, w którym przywróciłem z powrotem pętlę z tysiącem iteracji tak, że te pięć stałych miało pokazać się 1000 razy. Pomiary identycznie, jak w testach 1 i 3.

  1. Stałe globalne: t = 2,6797 s; u(t) = 0,0080 s
  2. Stałe klasowe: t = 3,0501 s; u(t) = 0,0059 s

Stałe globalne znów wygrały.

Interpretacja

Prawda o zwiększonej wydajności stałych klasowych jest tylko połowiczna. Potwierdzam, że w części sytuacji ich użycie przyspiesza kod, ale paradoksalnie nie z powodów podanych przez twórców. Zauważmy, że wszędzie tam, gdzie wygrały stałe klasowe, występowała dość mała ilość odwołań do stałych: w teście 1 było ich 10, w teście 3 zaledwie 5, za to pojawiło się tam aż 30 deklaracji. Wychodzi na to, że najzwyczajniej w świecie stałe klasowe się znacznie szybciej inicjują (bardzo możliwe, że robione jest to częściowo już w momencie kompilacji, ale trzeba by przeanalizować źródła, by to potwierdzić).

Między bajki można natomiast włożyć opowieści, że stałe klasowe są szybsze, ponieważ wewnętrzna przestrzeń klasy jest znacznie mniejsza, a przez to łatwiejsza do przeszukania. Moja interpretacja jest następująca:

  1. Najpierw trzeba odnaleźć samą klasę, a tych też jest trochę.
  2. PHP do przechowywania różnych zbiorów wykorzystuje szeroko tablice z haszowaniem. Z tego, co pamiętam, w momencie inicjacji można określić ilość kubełków, wybierając od bardzo małej do ogromnej. Średni czas dostępu jest proporcjonalny do średniej ilości rekordów w jednym kubełku. Jeśli tablica na stałe globalne ma np. 3000 kubełków, a stałych jest wszystkich 2000, można się spodziewać, że dostawać się będziemy do nich w czasie jednostkowym. Tak więc albo twórcy źle dobrali rozmiary, albo wręcz zrezygnowali w tym miejscu z tablic z haszowaniem.

Zwróćmy jednak uwagę, że w większości skryptów zadeklarowane są setki stałych, a w praktyce podczas całego przetwarzania żądania korzysta się tylko z kilkudziesięciu z nich. Ba - często jest zbiór np. 40 stałych reprezentujących możliwe ustawienia, a programista ma z niego w jednym miejscu wybrać jedną i o reszcie może zapomnieć. Tak więc tutaj znacznie szybsza kompilacja będzie atutem, który nie tylko zniweluje narzut czasowy związany z odwołaniami, ale też w w ogólności przyspieszy całość.

Powrót

Załączniki:

Komentarze

Napisał KODON w wtorek, 17 czerwca 2008 o 16:53

Odczyt elementów znajdujących się wewnątrz klas jest wolniejszy, więc wynik do przewidzenia. :) Twórcy PHP powinni zoptymalizować OOP oraz czas wywołania funkcji i metod. Może w PHP 6 coś się zmieni.

Przydałby się pomiar szybkości wyświetlania stron zakodowanych w UTF-8 oraz ISO-8859-2 przez przeglądarki. Może ktoś trafił na taki w sieci Webb?

Napisał Nowaker w wtorek, 17 czerwca 2008 o 17:16

Ciekawy test, choć bardziej hobbystyczny, niż praktyczny ;) Różnica czasów na którymś miejscu po przecinku nie będzie powodem do stosowania pierwszego bądź drugiego typu w swoich skryptach, lecz od czytelności kodu.

Patrząc na te różnice można sobie przypomnieć, że pomimo tego że piszemy na wyższym poziomie abstrakcji, to wszystko jednak przekłada się na zwykły kod asemblera.

Napisał Kłeczek Marcin w wtorek, 17 czerwca 2008 o 17:57

Czy prędkość dostępu do stałej jest tak ważna? Jest wiele innych miejsc, które można (trzeba!) optymalizować, że je takimi rzeczami się nie zajmuję (chętnie poczytam, ale żeby tracić czas na "benczmarkowanie" ;) ). Ważniejsze są możliwości i ograniczenia - BTW w jaki sposób pobrać stałe dla klasy/obiektu?

Napisał Zyx w wtorek, 17 czerwca 2008 o 21:38

Zgadzam się, że jeśli rozpatrujemy pojedynczy ulepszacz, wyniki będą śmiesznie małych rozmiarów. Jednak jest pewna rzecz, którą świetnie podsumowuje znaleziony dziś cytat Stanisława Jerzego Leca: "Nie zgadzam się z matematyką. Uważam, że suma zer daje całkiem groźną liczbę." W końcu żyjemy w świecie, w którym programiści potrafią wymyślić Javę, później napisać w niej ogromne środowsko programistyczne i na koniec narzekać, jak to wszystko @#$@#$ wolno chodzi... :P

Napisał Nowaker w wtorek, 17 czerwca 2008 o 21:52

Z tą Javą trochę prawdy, ale też nie do końca ;) Patrząc na programy NetBeans i Azureus przekleństwa cisną się na usta, ale praca z Eclipse jest już całkiem przyjemna, mimo że również jest w Javie.

Napisał mik01aj w wtorek, 17 czerwca 2008 o 22:48

"Pliki testów i wyniki (1 b)"
Wow! co to za algorytm kompresji? :D

Napisał Kłeczek Marcin w wtorek, 17 czerwca 2008 o 23:02

Nikt nie odpowiedział na moje pytanie - w jaki sposób pobrać wszystkie stałe klasy (odpowiednik get_defined_constants()).

Wracając do optymalizacji - faktycznie są różnice w przypadku tych "rzeczy", ale optymalizacją tego powinni zająć się core developerzy PHP - programiści PHP powinni skupić się na czytelności, łatwości modyfikacji programów. Duża ilość zmiennych globalnych niestety tego nie ułatwia...

Napisał KODON w środę, 18 czerwca 2008 o 00:13

PHP 5 bez dodatków nie obsługuje w pełni kodowania UTF-8. Użycie standardowych funkcji na ciągach zawierających wielobajtowe znaki (np. polskie litery) może spowodować ich rozsypkę. Można pytać - dlaczego? Przecież UTF-8 jest powszechnie używany już od dawna.

Wykonałem test porównawczy szybkości funkcji operujących na ciągach znaków - standardowych i mbstring. Okazuje się, że te drugie, które obsługują UTF-8, są wolniejsze. http://www.unit1.pl/pb-805

Napisał Nowaker w środę, 18 czerwca 2008 o 12:29

@Marcin, zainteresowałem się tym pytaniem, bo rzeczywiście nie ma czegoś w stylu get_defined_class_constants(). Po dłuższych poszukiwaniach znalazłem. ReflectionClass->getConstants(). http://pl.php.net/manual/pl/language.oop5.reflection.php

Bałaganiarswo PHP jest wkurzające.

Napisał Kłeczek Marcin w środę, 18 czerwca 2008 o 21:21

@Nowaker dzięki, szukałem, szukałem... na szczęście Ty znalazłeś (a ja w międzyczasie machnąłem obejście parsujące źródło klasy i zapisujące zmienne do... innego źródła :) ). Dzięki za pomoc.

Swoją drogą, to chyba jest następny argument w naszej niedawnej "rozmowie" blogowej BTW programowania obiektowego...

Napisał Vane w piątek, 20 czerwca 2008 o 18:04

Chciałbym zapytać dlaczego wcześniejszy komentarz innego użytkownika, który zresztą dość słusznie skrytykował podejście zaprezentowane w całym artykule został skasowany? Pisałeś swego czasu że panuje tu wolność wypowiedzi o ile nie obrażają nikogo a to wygląda jak cenzura.

Napisał Zyx w sobotę, 21 czerwca 2008 o 10:32

Ponieważ komentarz naruszał dwie zasady panujące na Zyxist.com:
1. Nie komentuje się wpisów, których się nie czytało, tak samo jak nie polemizuje się z argumentacją, z którą się nie zapoznało. Autor komentarza na wstępie napisał, że wpisu nie czytał i samo to jest wystarczającym powodem do dyskwalifikacji.
2. ... co się zemściło w postaci komentarza nie na temat - pan Sędziwój wyśmiał to, że porównuję zmienne globalne i klasowe, chociaż nawet w tytule wpisu jak byk stoi słowo stałe.

Skoro wyjątków od tego nie było od 1,5 roku, to stwierdziłem, że nie ma sensu robić teraz. Zgodnie z kolejną zasadą, autor komentarza został powiadomiony e-mailem, dlaczego go usunąłem. Zauważ, że całą resztę krytycznych komentarzy do tego wpisu puściłem. Właśnie dlatego, że były z sensem, na temat i nie zawierały klauzuli dyskwalifikacyjnej :).

Nawiasem mówiąc w "Hall of fame" było i jest napisane, kiedy komentarze nie są publikowane. Trochę to przeorganizowałem teraz w formie podpunktów, żeby było prościej i jaśniej.

Strona 1 z 1 :: 1

Skomentuj

NickInformacja
E-mailTylko do użytku wewnętrznego.
WWWNie zapomnij o http://
LayoutNapisz tu, czy widzisz dzienny czy nocny layout.
WpisFormatowanie wiki
Internauto, pamiętaj! Wolność to nie samowola - dbaj o kulturę wypowiedzi oraz dyskusji w sieci.

Na Zyxist.com panuje swoboda wyrażania opinii oraz krytyki pod dowolnym adresem. Jedyny warunek: musi być ona kulturalna i rzeczowa. Na chamstwo, prostactwo lub jawne obrażanie kogokolwiek nie ma tu miejsca i takie komentarze są bardzo szybko usuwane. Jeśli zamierzasz polemizować z treścią wpisu, wpierw uważnie ją przeczytaj.

© Tomasz "Zyx" Jędrzejewski 2005 - 2008 | Wykonanych zapytań: 2 | Serwer wirtualny zapewnia