PHP Sebességteszt

php js c#

A PHP nyelv nem egy gyors nyelv, viszont nagyon rugalmas ezért sokan előszeretettel végeznek bonyolult számításokat is vele.

Ezért vegyük sorra, hogyan nyerhetők értékes másodpercek egy-egy nagyobb több (száz)ezres ciklusnál.

 
 
 

Figyelem! Az oldalon mutatott, mért sebességek a PHP kódot futtató szervertől (géptől) függően eltérhetnek, csak tájékoztató értékűek! Azonban az átlagosan elért eredmények átlaga jól mutatja, hogy melyik kóddal milyen eredményeket lehet elérni megfelelően magas iteráció szám felett!

Vezérlési szerkezetek (if, else, switch kombinációk):

Az elágazások a programozás során elengedhetetlen kellékek, ezért rögtön velük kezdjük

Iterációk száma: 1000

if és elseif (== vizsgálattal): % µsFrissítKód részlet
$answer = 2;
$iteration = 1000;
while($i < $iteration) {

        if($answer == 1) {

        } else if($answer == 2) {

        }

        ++$i;
    }
if, elseif és else (== vizsgálattal): % µsFrissítKód részlet
$answer = 2;
$iteration = 1000;
while($i < $iteration) {

    if($answer == 1) {

    } else if($answer == 3) {

    } else {

    }

    ++$i;
}
if, elseif (=== vizsgálattal): % µsFrissítKód részlet
$answer = 2;
$iteration = 1000;
while($i < $iteration) {

    if($answer === 1) {

    } else if($answer === 2) {

    }

    ++$i;
}
if, elseif és else (=== vizsgálattal): % µsFrissítKód részlet
$answer = 2;
$iteration = 1000;
while($i < $iteration) {

    if($answer === 1) {

    } else if($answer === 3) {

    } else {

    }

    ++$i;
}
switch - case: % µsFrissítKód részlet
$answer = 2;
$iteration = 1000;
while($i < $iteration) {

    switch($answer) {

        case 1:

            break;

        case 2:

            break;
    }

    ++$i;
}
switch - case - default: % µsFrissítKód részlet
$answer = 2;
$iteration = 1000;
while($i < $iteration) {

    switch($answer) {

        case 1:

            break;

        case 3:

            break;

        default:

            break;
    }

    ++$i;
}
két ágú if-else teszt: % µsFrissítKód részlet
$answer = 2;
$iteration = 1000;
while($i < $iteration) {

    if($answer == 1) {
        $x = 1;
    } else {
        $x = 2;
    }

    ++$i;
}
két ágú egysoros if, var = ()?:; % µsFrissítKód részlet
$answer = 2;
$iteration = 1000;
while($i < $iteration) {
    $x = ($answer==1)?1:2;
    ++$i;
}

Konklúzió:

Egy-két ezer iterációnál mindegy, hogy switch-case vagy if-else vezérlést valósítunk meg, azonban egyenlőség vizsgálatnál a (=== típusazonos összehasonlító operátor) jelentősebb sebesség növekedést mutat a (== összehasonlító operátor)-hez képest!

Érdekesség, hogy a kedvelt (már akinél) egysoros if (utolsó 2 teszt) átlag 6-7%-kal lassabb volt a normál if-else szerkezetnél.

Normál számláló ciklusok tesztje (Counting-Loop):

A sima for() és while() ciklusokat hasonlítja egymáshoz, terhelés nélkül.

Iterációk száma: 1000000

for() % µsFrissítKód részlet
    for($i = 0; $i < 1000000; ++$i);
while() % µsFrissítKód részlet
    $i = 0; while($i < 1000000) ++$i;

Konklúzió:

Szinte minden alkalommal a while() volt a gyorsabb átlag 8%-kal.

Folytonos ciklusok (For-Loop):

A ciklusokban rengeteg időt spórolhatunk, ha lehetőség szerint előre kiszámítjuk egy ciklus várható iterációinak számát!

Iterációk száma: 1000

Előre kiszámított - count(): % µsFrissítKód részlet
    $i   = 0; $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $x = array_fill(5, 1000, $tmp);
    unset($i, $tmp);

    $size = count($x);
    for ($i=0; $i < $size; $i++);
Előre kiszámítás nélküli - count(): % µsFrissítKód részlet
    $i   = 0; $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $x = array_fill(5, 1000, $tmp);
    unset($i, $tmp);

    for ($i=0; $i < count($x); $i++);
Előre kiszámított - sizeof() % µsFrissítKód részlet
    $i   = 0; $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $x = array_fill(5, 1000, $tmp);
    unset($i, $tmp);

    $size = sizeof($x);
    for ($i=0; $i  < $size; $i++);
Előre kiszámítás nélküli - sizeof() % µsFrissítKód részlet
    $i   = 0; $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $x = array_fill(5, 1000, $tmp);
    unset($i, $tmp);

    for ($i=0; $i < sizeof($x); $i++);

Konklúzió:

A count() és sizeof() között nincs lényegi különbség, viszont az eredmény abszolút magáért beszél.

Ha előre kiszámítjuk a hurkok számát, akkor rengeteg időt nyerünk, természetesen azért mert ha a for ciklusba ágyazzuk a count() vagy sizeof()-t, akkor minden egyes iterációban kiszámolja a (már előre úgy is tudott) eredményt!

Felsorolás alapú ciklusok (Read-Loop):

Keressük meg melyik megoldás a leghatékonyabb egy hash tömböt végig pörgetni.
foreach() vs. for() vs. while(list() = each())

A teszthez egy 100 elemű, bejegyzései 24byte-os kulcsot és 10k-nyi adatot tartalmaznak.

foreach($aHash as $val); % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    foreach($aHash as $val);
while(list(,$val) = each($aHash)); % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    while(list(,$val) = each($aHash));
foreach($aHash as $key => $val); % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    foreach($aHash as $key => $val);
while(list($key,$val) = each($aHash)); % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    while(list($key,$val) = each($aHash));
foreach($aHash as $key=>$val) $tmp[] = $aHash[$key]; % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    foreach($aHash as $key=>$val) $tmp[] = $aHash[$key];
while(list($key) = each($aHash)) $tmp[] = $aHash[$key]; % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    while(list($key) = each($aHash)) $tmp[] = $aHash[$key];
Get key-/ value-array: foreach($aHash as $key[]=>$val[]); % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    foreach($aHash as $key[] => $val[]);
Get key-/ value-array: array_keys() / array_values() % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    array_keys($aHash);
    array_values($aHash);
Get key, és for ($i=0; $i<$size; $i++) $tmp[] = $aHash[$key[$i]]; % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    $key = array_keys($aHash);
    $size = sizeOf($key);
    for ($i=0; $i < $size; $i++) $tmp[] = $aHash[$key[$i]];

Konklúzió:

Minden esetben kitűnik, hogy a foreach() jóval gyorsabb, mind a while(), mind a for() ciklusoknál.

Felsorolás alapú ciklusok terheléses tesztje (Modify-Loop):

Ez a teszt szintén egy hash tömbön fut végig, de közben módosítást is végez rajta.
foreach() vs. for() vs. while()

A teszthez egy 100 elemű, bejegyzései 24byte-os kulcsot és 10k-nyi adatot tartalmaznak.

foreach() % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    foreach($aHash as $key=>$val) $aHash[$key] .= "a";
while() % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    reset($aHash);
    while(list($key) = each($aHash)) $aHash[$key] .= "a";
for() % µsFrissítKód részlet
    $i   = 0;
    $tmp = '';
    while($i < 10000) {
      $tmp .= 'a';
      ++$i;
    }
    $aHash = array_fill(100000000000000000000000, 100, $tmp);
    unset($i, $tmp);

    $key = array_keys($aHash);
    $size = sizeOf($key);
    for ($i=0; $i<$size; $i++) $aHash[$key[$i]] .= "a";

Konklúzió:

A foreach() és for() render egymás mellett végeztek, de 10ből 9-szer a foreach() picit jobb volt. A while() láthatóan, rendre lemaradt átlag 35%-kal.

String kiíratás:

Az echo és a print tesztje, sima stringekkel, összefűzve változóval...

Iterációk száma: 1000

echo 'HelloHelloHelloHelloHelloHello'; % µsFrissítKód részlet
    while($i < 1000) {
        echo 'HelloHelloHelloHelloHelloHello';
        ++$i;
    }
print 'HelloHelloHelloHelloHelloHello'; % µsFrissítKód részlet
    while($i < 1000) {
        print 'HelloHelloHelloHelloHelloHello';
        ++$i;
    }
echo 'Hello'.'Hello'.'Hello'.'Hello'.'Hello'.'Hello'; % µsFrissítKód részlet
    while($i < 1000) {
        echo 'Hello'.'Hello'.'Hello'.'Hello'.'Hello'.'Hello';
        ++$i;
    }
echo 'Hello','Hello','Hello','Hello','Hello','Hello'; % µsFrissítKód részlet
    while($i < 1000) {
        echo 'Hello','Hello','Hello','Hello','Hello','Hello';
        ++$i;
    }
print 'Hello'.Hello'.'Hello'.'Hello'.'Hello'.'Hello'; % µsFrissítKód részlet
    while($i < 1000) {
        print 'Hello'.'Hello'.'Hello'.'Hello'.'Hello'.'Hello';
        ++$i;
    }
a$ = 'Hello'; echo 'Hello'.$a.'Hello'.$a.'Hello'.$a; % µsFrissítKód részlet
    while($i < 1000) {
        echo 'Hello'.$a.'Hello'.$a.'Hello'.$a;
        ++$i;
    }
a$ = 'Hello'; echo 'Hello',$a,'Hello',$a,'Hello',$a; % µsFrissítKód részlet
    while($i < 1000) {
        echo 'Hello',$a,'Hello',$a,'Hello',$a;
        ++$i;
    }
a$ = 'Hello'; print 'Hello'.$a.'Hello'.$a.'Hello'.$a; % µsFrissítKód részlet
    while($i < 1000) {
        print 'Hello'.$a.'Hello'.$a.'Hello'.$a;
        ++$i;
    }

Konklúzió:

Az üres string kiírást mellőztem a tesztből mert elég kevés a gyakorlati haszna. Az echo a printnél alapból úgy 10%-kal jobban teljesített, azon kívűl látható, hogy a string összefűzésnél ('xx'.'xx') gyorsabb (nagyszámú iterációnál), ha a nem string összefűzést csinálunk, hanem az echo-t hívjuk meg egymás után többször ('xx','xx').

Valamint nem érdemes az előre ismert egybefüggő ('aaaa') szövegeket ('aa'.'aa') egyesíteni, hiszen jóval lassabb futást eredményez.

Ha változókkal is vegyítjük a kimenetet, akkor is jól látható, hogy az echo a jobb választás, még pedig a többszörös meghívással ('xx',$x)

Változó tipusának vizsgálata:

isSet() vagy empty(), valamint is_array()

Iterációk száma: 1000

isset() előre beállított változóval % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';
    
    while($i < 1000) {
        isSet($isset);
        ++$i;
    }

empty() előre beállított változóval % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';
    
    while($i < 1000) {
        empty($isset);
        ++$i;
    }
isset() nem létező változóval % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';

    while($i < 1000) {
        isset($notset);
        ++$i;
    }
empty() nem létező változóval % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';

    while($i < 1000) {
        empty($notset);
        ++$i;
    }
isset() tömb változóval % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';

    while($i < 1000) {
        isset($array);
        ++$i;
    }
empty() tömb változóval % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';

    while($i < 1000) {
        empty($array);
        ++$i;
    }
is_array() tömb változóval % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';

    while($i < 1000) {
        is_array($array);
        ++$i;
    }
is_array() string változóval % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';

    while($i < 1000) {
        is_array($string);
        ++$i;
    }
is_array() nem létező változóval % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';

    while($i < 1000) {
        is_array($notset);
        ++$i;
    }
is_array() nem létező változóval, kombinálva isset()-tel % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';

    while($i < 1000) {
        if (isset($notset) && is_array($notset)){;}
        ++$i;
    }
is_array() létező változóval, kombinálva isset()-tel % µsFrissítKód részlet
    $isset       = true;
    $array       = array();
    $string      = '';

    while($i < 1000) {
        if (isset($array) && is_array($array)){;}
        ++$i;
    }

Konklúzió:

Az isset() és az empty() is fej-fej mellett teljesít, mind létező mind nem létező változónál.

Az is_array()-nál viszont jelentős sebességbeli kilengések tapasztalhatóak a változó típusától függően, valamint ha nincs beállított változó akkor jelentősen lassul az is_array()!

Az is_array()-t nagyon is érdemes az isset()-tel együtt használni egy feltételben (lásd utolsó 2 teszt), mert egy if-n belül ha az isset()-nél megbukott a feltétel, akkor nem folytatja a php vizsgálatot, így jelentős gyorsulás érhető el ha nem volt a változó beállítva! (Ellenben egy csekély lassulás betudható a plusz vizsgálatnak)

Tömbök elérések

Tömbök elérésének összehasonlítása érték és referencia alapon.

Iterációk száma: 1000

$x = $array[$i]; % µsFrissítKód részlet
//feltöltés
for($i=0; $i < 1000;$i++)
    $array[$i] = 'HelloHello';
//mérés
$t = microtime(true);

for($i = 0; $i < 1000; $i++)
    $x = $array[$i];
    
return (microtime(true) - $t)* 1000000;
$x = &$array[$i]; % µsFrissítKód részlet
//feltöltés
for($i=0; $i < 1000;$i++)
    $array[$i] = 'HelloHello';
//mérés
$t = microtime(true);

for($i = 0; $i < 1000; $i++)
    $x = &$array[$i];

return (microtime(true) - $t) * 1000000;
$x = $array2[$i][0]; % µsFrissítKód részlet
//feltöltés
for($i=0; $i < 1000;$i++)
    for($ii=0; $ii < 10;$ii++)
        $array2[$i][$ii] = 'HelloHello';
//mérés
$t = microtime(true);

for($i = 0; $i < 1000; $i++)
    $x = $array2[$i][0];
    
return (microtime(true) - $t)*1000000;
$x = &$array2[$i][0]; % µsFrissítKód részlet
//feltöltés
for($i=0; $i < 1000;$i++)
    for($ii=0; $ii < 10;$ii++)
        $array2[$i][$ii] = 'HelloHello';
//mérés
$t = microtime(true);

for($i = 0; $i < 1000; $i++)
    $x = &$array2[$i][0];

return (microtime(true) - $t) * 1000000;
$x = $array4[$i][1][2][3]; % µsFrissítKód részlet
//feltöltés
for($i=0; $i < 1000;$i++)
    for($ii=0; $ii < 7;$ii++)
        for($iii=0; $iii < 7;$iii++)
            for($iiii=0; $iiii < 7;$iiii++)
                $array4[$i][$ii][$iii][$iiii] = 'HelloHello';
//mérés
$t = microtime(true);

for($i = 0; $i < 1000; $i++)
    $x = $array4[$i][1][2][3];
    
return (microtime(true) - $t)*1000000;
$x = &$array4[$i][1][2][3]; % µsFrissítKód részlet
//feltöltés
for($i=0; $i < 1000;$i++)
    for($ii=0; $ii < 7;$ii++)
        for($iii=0; $iii < 7;$iii++)
            for($iiii=0; $iiii < 7;$iiii++)
                $array4[$i][$ii][$iii][$iiii] = 'HelloHello';
//mérés
$t = microtime(true);

for($i = 0; $i < 1000; $i++)
    $x = &$array4[$i][1][2][3];

return (microtime(true) - $t) * 1000000;
$x = $array8[$i]['H']['He']['Hel']['Hell']['Hello']['HelloH']['HelloHe']; % µsFrissítKód részlet
//feltöltés
for($i=0; $i < 1000;$i++)
    $array8[$i]['H']['He']['Hel']['Hell']['Hello']['HelloH']['HelloHe'] = 'HelloHello';
//mérés
$t = microtime(true);

for($i = 0; $i < 1000; $i++)
    $x = $array8[$i]['H']['He']['Hel']['Hell']['Hello']['HelloH']['HelloHe'];
    
return (microtime(true) - $t)*1000000;
$x = &$array8[$i]['H']['He']['Hel']['Hell']['Hello']['HelloH']['HelloHe']; % µsFrissítKód részlet
//feltöltés
for($i=0; $i < 1000;$i++)
    $array8[$i]['H']['He']['Hel']['Hell']['Hello']['HelloH']['HelloHe'] = 'HelloHello';
//mérés
$t = microtime(true);

for($i = 0; $i < 1000; $i++)
    $x = &$array8[$i]['H']['He']['Hel']['Hell']['Hello']['HelloH']['HelloHe'];

return (microtime(true) - $t) * 1000000;

Konklúzió:

Közel egyforma értékeket produkált mind az érték, mind a referenciakénti tömb lekérdezés, illetve az asszociatív címzésnél a referenciakénti átadás elkezdett lassulni. Azonban ne feledjük, hogy a referencia szerinti paraméterátadás nagyobb objektumok, tömbök esetén jelentősen gyorsabb mint az érték szerinti.

Régebben a jobb teljesítmény elérése miatt nagy figyelmet kellett fordítani a paraméterátadás eme módjára, viszont a PHP 5.0 óta megjelent a "copy on write" technika, melynek lényege, hogy az érték szerint átadott paraméter nem másolódik le a hívás pillanatában.
A másolás csak abban az esetben történik meg, ha módosításra, írásra kerülne sor.

Az oldal PHP-s része a www.phpbench.com alapján készült, az ott felsorolt méréseket vette alapul, de attól eltérhetnek!
Az oldalt készítette Szentgyörgyi János @ dynamicart.hu | 2011.
Creative Commons Licenc