20 06 2011

Zaległy artykuł, odpowiedź do ZyXa.

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.

kategoria: Tworzymy bibliotekę autor: Michał Juszczak

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

Komentarz od ZyXa (z dnia 2010-10-26)

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++.

Dodaj komentarz:

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.

Komentarze:

Ostatnie wiadomości:

Ostatnie komentarze:

  • (2010-10-26) Nie wiem, że metody statyczne są szybsze i nie wiem, skąd bierzesz tak...
    NEWS Tworzenie biblioteki Underscore: 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 pseudoobiektowego śmietnika. Ponadto co z takimi kwestiami, jak niezawodność i elastyczność? Zyx (grrrr, mój nick jest trzyliterowy!)
  • (2010-10-26) Niestety, takie rozwiązanie jest wybitnie niepraktyczne. Już to przera...
    NEWS Konfiguracja klas i obiektów - metoda settings: Niestety, takie rozwiązanie jest wybitnie niepraktyczne. Już to przerabiałem spory kawałek czasu temu przy okazji tworzenia Open Power Libs 2.0 i do dziś odbija mi się to czkawką. Po pierwsze, używasz do tego elementów statycznych, czyli już na wejściu utrudniasz sobie testowanie i wprowadzasz elementy niedeterminizmu, które mogą być źródłem podobnych problemów, jakie powoduje "global". Po drugie, Twoja funkcja do ładowania konfiguracji też jest niepraktyczna, bowiem wymaga uprzedniego załadowania wszystkich możliwych klas nawet, jeśli ich nie używamy, gdyż inaczej oberwiemy wyjątkiem. Dobrym, sprawdzonym rozwiązaniem jest idea wstrzykiwania zależności. PS. Masz u mnie minusa za nałożenie limitu min. 5 znaków na długość nicka, który jest idiotyczny, jako że jest całkiem sporo osób, które mają czteroliterowe nicki, a trzyliterowe takie, jak mój, też się trafiają. Zyx (grrrr, mój nick jest trzyliterowa!!!)

Kategorie:

Tworzymy bibliotekę Tworzenie stron internetowych Zarządzenie przedsiębiorstwem Blog Zarządzanie jakością