Jest to zaległy artykuł który pisałem w listopadzie 2010 roku. Gdzieś przepadł na dysku i leżał i czekał. Poprawiając strony znalazłem go i chciałem od razu puścić w "eter"; jednakże coś mnie pokusiło i jeszcze raz sprawdziłem wyniki. Kurcze, wychodzi całkowicie coś innego. Artykułu nie przerobię, ale dodam kilka uwag. Komentarze które napisałem wczoraj/dzisiaj będą wyróżnione.
Przez problemy w sferze osobistej musiałem zawiesić działanie bloga (ale napewno wrócę), jak i również tworzenie biblioteki Underscore (link). W międzyczasie otrzymałem dwa komentarze do artykułów. Oba od ZyXa (link). W tym artykule dam odpowiedź na pytanie, dodatkowo przepraszam Cię ZyX za narzucone minimum 5 znaków przy nazwie (komentarze). Jak tylko zmienię miejsce zamieszkania, a planuje do końca roku dam odpowiedź na drugie, jak również dodam artykuł o konfiguracji notepad++ by sprawnie działał i nie stwarzał problemów. No to zaczynajmy.
Miało być na początku roku, a wyszło jak zawsze...
Nie wiem, że metody statyczne są szybsze i nie wiem, skąd bierzesz takie rewelacje, bo odkąd pamiętam, zawsze było na odwrót. Nawet przed chwilą zrobiłem szybki benchmark i nic się nie zmieniło w tym względzie. Ponadto z tego, co piszesz (zwłaszcza o krótkości kodu), wydaje mi się, że nie rozumiesz sensu programowania obiektowego. Robienie metod typu wordCount() to głupota do kwadratu - przecież po to się robi obiekty, by te metody pracowały w ich kontekście i wykonywały coś na nich, a nie działały tak sobie w próżni. O wiele lepsze wyniki wydajnościowe osiągniesz dobrymi algorytmami i odpowiednią architekturą, a nie robieniem sobie pseudo-obiektowego śmietnika. Ponadto co z takimi kwestiami, jak niezawodność i elastyczność?
Moja odpowiedź na komentarz jest dłuższa. Zacznijmy od szybkości wywoływania funkcji i metod. Z moich obserwacji najszybsze jest wywoływanie kodu bezpośrednio (linia po linii), następnie funkcje, potem metody wywoływane statycznie a następnie metody w obiektach. Obrazuje to, dosyć topornie niniejszy kod (pisałem go na szybko):
// ilość pętli porównawczych
define ('LOOP', 50);
// ilość można tak powiedzieć opóźnień w kodzie (wiadomo tabele, liczenie etc)
define ('INSIDELOOP', 1000);
//
// definiujemy funkcję
//
function testMe()
{
for ($_i=0; $_i<=100; $_i++) {
$_doSomethingWeird = '';
}
}
//
// definiujemy klasę z metodą statyczną
//
class testStatic
{
static public function testMe()
{
for ($_i=0; $_i<=100; $_i++) {
$_doSomethingWeird = '';
}
}
}
//
// definiujemy klasę z metodą wywoływaną przez obiekt
//
class testObject
{
public function testMe()
{
for ($_i=0; $_i<=100; $_i++) {
$_doSomethingWeird = '';
}
}
}
// zerujemy tablicę wyników
$_results = array();
// no i pętla wykonywania
//
// metoda statyczna
//
for ($_loop=0; $_loop<=LOOP; $_loop++) {
$_start = explode(" ", microtime());
$_start = $_start[0] + $_start[1];
for ($_j=0; $_j<=INSIDELOOP; $_j++) {
testStatic::testMe();
}
$_stop = explode(" ", microtime());
$_stop = $_stop[0] + $_stop[1];
$_results['static'][$_loop] = $_stop-$_start;
}
// no i pętla wykonywania
//
// metoda z obiektu tworzonego jednokrotnie
//
for ($_loop=0; $_loop<=LOOP; $_loop++) {
$_test = new testObject();
$_start = explode(" ", microtime());
$_start = $_start[0] + $_start[1];
for ($_j=0; $_j<=INSIDELOOP; $_j++) {
$_test->testMe();
}
$_stop = explode(" ", microtime());
$_stop = $_stop[0] + $_stop[1];
$_results['object'][$_loop] = $_stop-$_start;
}
// no i pętla wykonywania
//
// metoda z obiektu tworzonego w każdej pętli
//
for ($_loop=0; $_loop<=LOOP; $_loop++) {
$_start = explode(" ", microtime());
$_start = $_start[0] + $_start[1];
for ($_j=0; $_j<=INSIDELOOP; $_j++) {
$_test = new testObject();
$_test->testMe();
unset($_test);
}
$_stop = explode(" ", microtime());
$_stop = $_stop[0] + $_stop[1];
$_results['objectX'][$_loop] = $_stop-$_start;
}
// no i pętla wykonywania
//
// funkcja
//
for ($_loop=0; $_loop<=LOOP; $_loop++) {
$_start = explode(" ", microtime());
$_start = $_start[0] + $_start[1];
for ($_j=0; $_j<=INSIDELOOP; $_j++) {
testMe();
}
$_stop = explode(" ", microtime());
$_stop = $_stop[0] + $_stop[1];
$_results['function'][$_loop] = $_stop-$_start;
}
// no i pętla wykonywania
//
// sam kod wklejony w pętl
//
for ($_loop=0; $_loop<=LOOP; $_loop++) {
$_start = explode(" ", microtime());
$_start = $_start[0] + $_start[1];
for ($_j=0; $_j<=INSIDELOOP; $_j++) {
for ($_i=0; $_i<=100; $_i++) {
$_doSomethingWeird = '';
}
}
$_stop = explode(" ", microtime());
$_stop = $_stop[0] + $_stop[1];
$_results['clean'][$_loop] = $_stop-$_start;
}
//
// zrób średnią
//
foreach ($_results as $_name=>$_sum) {
echo $_name.' - '.(array_sum($_sum)/LOOP)."<br />";
}
//
// wyświetl mnie
//
print_r ($_results);
Test ten wykonywałem na laptopie dwurdzeniowym niskobudżetowym kilka miesięcy temu na maszynie WinXP + PHP 5.2. Wyniki prezentowały się następująco (kilka odświeżeń strony), test obiektu tworzonego w każdej pętli dodany został teraz:
| Podejście | clean | function | static | object |
|---|---|---|---|---|
| 1 | 0.0529410505295 | 0.0609126091003 | 0.0635455989838 | 0.0647043132782 |
| 2 | 0.0531651735306 | 0.0609511852264 | 0.0634791326523 | 0.0688288354874 |
| 3 | 0.053200507164 | 0.061169719696 | 0.0635678577423 | 0.0650468063354 |
| 4 | 0.0533508491516 | 0.0614563179016 | 0.0635803842545 | 0.0655667209625 |
| 5 | 0.0528730630875 | 0.0609019565582 | 0.0631151008606 | 0.0649032115936 |
| Średnia | 0.0531061286926 | 0.0610783576965 | 0.0634576148987 | 0.0658099775314 |
| % do clean | 100% | 115% | 119% | 124% |
Co widać na załączonym obrazku metody obiektowe wykonują się najdłużej, ale to jest mój benchmark, może na innym wyniki będą zaprezentowane inaczej. Ciekawi mnie jedna rzecz. Proszę zwrócić uwagę na pierwsze trzy. Rozbieżność w granicy 0.001sek. Natomiast w przypadku obiektu widoczne jest wahanie, ale może wtedy coś zaszamotało dyskiem i wynik wyszedł ciut większy.
No i sprawdziłem wyniki przed publikacją. Teraz na maszynie stacjonarnej, teoretycznie lepszy sprzęt... otrzymałem takie wyniki (podwójny test obiektów):
| Podejście | clean | function | static | object | objectX |
|---|---|---|---|---|---|
| 1 | 0.097154111862183 | 0.17285912513733 | 0.12589478492737 | 0.12528561115265 | 0.1388111448288 |
| 2 | 0.092546191215515 | 0.16244168758392 | 0.12454387187958 | 0.13114392280579 | 0.12719962120056 |
| 3 | 0.096119027137756 | 0.15409189224243 | 0.12486216545105 | 0.14146304130554 | 0.13100878715515 |
| 4 | 0.091104922294617 | 0.15802936077118 | 0.12640028953552 | 0.12411957263947 | 0.12968328475952 |
| 5 | 0.092200665473938 | 0.15619870185852 | 0.12414258956909 | 0.12387494087219 | 0.12933528423309 |
| Średnia | 0.093824984 | 0.160724154 | 0.12516874 | 0.129177418 | 0.131207624 |
| % do clean | 100% | 171% | 133% | 138% | 140% |
Co jest? System i wersja PHP te same. Maszyna teoretycznie lepsza od laptopa. Zacząłem sprawdzać i problemem okazał się XDebug. Debugowanie i profilowanie. Po wyłączeniu otrzymałem znowu inne wyniki.
XDebug wyłączony całkowicie:
| Podejście | clean | function | static | object | objectX |
|---|---|---|---|---|---|
| 1 | 0.032870292663574 | 0.032584667205811 | 0.034955916404724 | 0.033946695327759 | 0.036105637550354 |
| 2 | 0.032583203315735 | 0.033130369186401 | 0.035458989143372 | 0.033386840820312 | 0.035856790542603 |
| 3 | 0.032203221321106 | 0.032311553955078 | 0.037029523849487 | 0.032837023735046 | 0.035464978218079 |
| 4 | 0.031730065345764 | 0.032662529945374 | 0.036598553657532 | 0.035539779663086 | 0.035478420257568 |
| 5 | 0.032589855194092 | 0.032943272590637 | 0.035785789489746 | 0.034171485900879 | 0.03579062461853 |
| Średnia | 0.032395328 | 0.032726479 | 0.035965755 | 0.033976365 | 0.03573929 |
| % do clean | 100% | 101% | 111% | 105% | 110% |
Dobra, wymiękam... czysty kod wiadomo będzie najszybszy. Potem teoretycznie proceduralne i potem obiektówka (ale jaka?). Takie testy zrobiłem. Wyszło na to, że XDebug bardziej ceni sobie static niż obiektówkę. Zastanawiające są wyniki funkcji. Przy wyłączonym XDebug najlepszym rozwiązaniem jest pojedyncze wywołanie obiektu a potem odwoływanie się do niego, np. przez rejestr. Statyczne niewiele gorsze od ciągłego tworzenia instancji klasy i odwoływania się do niej.
Sprawdźcie u siebie i podzielcie się uwagami.
Dalsza część komentarza dotyczyła architektury obiektowej. Wytłumaczę się od razu, rozumiem ją jak najbardziej, przykład z #wordCount# był takim, hmm... często wykorzystywaną funkcją. Tak naprawdę każdy z programistów ma swoją filozofię, ja umieszczam często wykorzystywane funkcje (bo metodami tego nie nazwę) we wrapperach jakimi są klasy i robię z tych funkcji metody statyczne. Ot, takie porządkowanie. Może nie podążam za aktualnymi trendami, ale jak to określić nobody is perfect. Oprę się jeszcze na tej #wordCount#, dlaczego tak wywoływana, a nie działająca bezpośrednio na obiektach? Odpowiem na to pytanie pytaniem. A czy funkcje PHP działają na obiektach? Trudno mi powiedzieć, czy mój kod jest pseudo-obiektowym śmietnikiem. Bibliotekę którą tworzę by mi pomogła nazwałem biblioteką a nie frameworkiem. To jest zestaw podręcznych funkcji i metod które mają mi pomóc w tworzeniu innych rozwiązań. Mam metody, które operują na tekście, są to proste metody (np. dodawanie za pojedynczymi literami - i, w, z, a, o ) i łatwiej mi zapamiętać, że wszystkie znajdują się w klasie _text. Wg mnie biblioteka ma ułatwić mi życie, umieszczam ją w internecie, gdyż ktoś może z niej skorzystać jeśli będzie chciał, ale oczywiście wykorzystam konstruktywną krytykę i zastanowię się nad poprawkami.
Wiele wody upłynęło i powoli moja filozofia się zmieniła od czasu gdy to pisałem. Zmieniłem architekturę biblioteki. Nie używam static, przeszedłem na rejestr. Ale nadal twierdzę, że nie ide zgodnie z nurtem; robię dla siebie i przez siebie. Jak skończę poprawiać wersję 0.7 to umieszczę w Internecie. Może komuś się przyda chociaż fragment kodu.
Co do ostatniego pytania od ZyXa - niezawodność i elastyczność. To jest bardziej problematyczne. Jeśli metoda jako parametr pobiera liczbę i zwraca liczbę, to ciężko stwierdzić jej elastyczność. Ma ona coś tam obliczyć i wszystko. Zły parametr lub brak rzutuje błąd, testy można wykonać i sprawdzić metodę.
Jestem programistą starej daty, często nie widzę potrzeby przy tworzeniach stron wykorzystywania zaawansowanego OOP, stosowania IOC i innych wstrzyknięć ;). PHP to nie JAVA, dla mnie jest to plusem tego języka. Uwielbiam miksować programowanie obiektowe z pseudo-obiektowym.
A teraz wracam do prywatnego życia. Pozdrawiam.
Jeden z pierwszych artykułów po powrocie. Już niedługo opis konfiguracji notepad++.
Komentarze podlegają moderacji, nie dopuszczam komentarzy spamujących, z wyzwiskami, wulgaryzmami, oszczerstwami. Rozumiem przez to również nie akceptowanie komentarzy, których treść jest prawnie zabroniona. Pozostałe komentarze, nawet takie które będą sprzeczne z moimi poglądami są akceptowane. Należy czekać na akceptację, każdy komentarz zostanie sprawdzony przeze mnie a następnie ukaże się na stronie.