Translate: 
EnglishFrenchGermanItalianPolishPortugueseRussianSpanish

Optymalizacja pętli w PHPie czyli 300% mocy

Mówi się, że człowiek uczy się całe życie – dziś po raz kolejny przekonałem się o słuszności tego powiedzenia. Siedzę już w PHP i ogólnie w programowaniu od dobrych paru lat i wydawało mi się, że proste błahostki nie potrafią mnie już zaskoczyć, myliłem się. W tym przypadku niespodzianką była dla mnie wydajność różnych rodzajów pętli. Po przypadkowym zapoznaniu się z tekstem o optymalizacji pętli w JavaScript na serwisie webreference.com postanowiłem sprawdzić jak PHP reaguje na takie zabiegi.

Na pierwszy ogień poszła standardowa pętla for dla $max = 1000000:

$t1 = microtime(true);
$j = 0;
for($i = 0; $i < $max; $i++) {
$j++;
}
$t2 = microtime(true);

Wynik: 0.40 ms


Jak wiadomo, w PHP inkrementowanie zmiennej poprzez $zmienna++ nie należy do najwydajniejszych, przed każdym zwiększeniem licznika PHP musi tymczasowo przechowywać jej poprzednią wartość. Zamiana kolejności operacji ze $zmienna++ na ++$zmienna przynosi więc wymierne korzyści:

$t1 = microtime(true);
$j = 0;
for($i = 0; $i < $max; ++$i) {
    $j++;
}
$t2 = microtime(true);

Wynik: 0.34 ms


W sieci krążą czasami pogłoski, jakoby w różnych językach (JavaScript, Python, itp) dekrementacja licznika była przynajmniej o 10% szybsza od inkrementowania, czy aby na pewno?

$t1 = microtime(true);
$j = 0;
for($i = $max; $i; --$i) {
    $j++;
}
$t2 = microtime(true);

Wynik: 0.34 ms

Jak widać w PHPie taki zabieg nie przynosi jednak spodziewanych efektów.


W języku PHP najczęściej spotykanym typem pętli jest pętla for / foreach. Pętle while oraz do-while kojarzą się przeciętnemu Kowalskiemu z czymś wolniejszym lub mniej eleganckim. W tym przypadku okazuje się jednak, iż w PHP osądy te są całkowicie niesłuszne, przyjrzyjmy się temu przykładowi:

$t1 = microtime(true);
$j = 0; $i = 0;
while($i < $max) {
    $j++;
    ++$i;
}
$t2 = microtime(true);

Wynik: 0.33 ms

Pętla ta może i jest bardziej rozbudowana od poprzednich przykładów, lecz już w swojej podstawowej postaci prezentuje wydajność porównywalną z najbardziej zoptymalizowanym odpowiednikiem pętli for.


To jednak nie koniec możliwych optymalizacji. Spróbujmy i w tym przypadku dekrementacji licznika oraz zamiany kolejności wykonywania poleceń z: while na do-while:

$t1 = microtime(true);
$j = 0; $i = $max;
do {
    $j++;
} while (--$i);
$t2 = microtime(true);

Wynik: 0.17 ms

Tym razem mamy niespodziankę, czas wykonania pętli zmniejszył się prawie o połowę w porównaniu do zwykłej pętli while oraz najszybszej wersji for. Nagła zmiana wynika przede wszystkim z faktu, iż PHP tak jak i większość innych języków lepiej radzi sobie z testem, czy zmienna jest różna od zera, niż przyrównywania jej do innej wartości niezerowej.


Przyjrzyjmy się jeszcze jednemu typowi "pętli", czyli mechanizmowi Duffa.

Oto przykładowy kod:

$t1 = microtime(true);
$j = 0;
$n = $max / 8;
$caseTest = $max % 8;  
do {
    switch($caseTest) {
        case 0: $j++;
        case 7: $j++; 
        case 6: $j++; 
        case 5: $j++; 
        case 4: $j++; 
        case 3: $j++; 
        case 2: $j++; 
        case 1: $j++; 
    }
    $caseTest = 0;
} while(--$n);
$t2 = microtime(true);

Wynik: 0.17 ms

Jak widać w przypadku PHP mechanizm ten w swojej pierwotnej postaci porównywalny jest pod względem wydajności ze zoptymalizowaną pętlą do-while. Wyprzedzając fakty: dodanie kolejnych "kroków" w konstrukcji switch'a (np z 8 do 16) nie zwiększa jego wydajności bardziej niż o 1ms.


W przeciwieństwie do mechanizmu Duff'a w JavaScripcie użycie switch'a nie przyniosło w PHP aż tak widocznych efektów, wypróbujmy więc jego zmodyfikowaną wersję:

$t1 = microtime(true);
$j = 0;
$n = $max % 8;
if($n > 0) {
    do {
        $j++;
    } while(--$n);
}
 
$n = $max / 8;  
do {
    $j++;
    $j++;
    $j++;
    $j++;
    $j++;
    $j++;
    $j++;
    $j++;
} while(--$n);
$t2 = microtime(true);

Wynik: 0,13 ms

Wynik mówi sam za siebie, jest to chyba najszybsza wersja pętli w PHP i wykonuje się ponad 3 razy szybciej niż standardowa pętla for.

Odmienną kwestią pozostaje odpowiedź na pytanie: kiedy optymalizować pętle? Oczywiście w samym języku PHP najbardziej czasochłonne są często inne części kodu (np. zapytania do bazy danych) i mikrooptymalizacje w takim przypadku są po prostu nieopłacalne. Przyspieszenie pętli największy sens ma za to przy przetwarzaniu dużej ilości danych (np operacje na tablicach i stringach) a także podczas operacji na grafice GD2.

Tagi: ,

Dodaj odpowiedź