Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

PHP: pomalé switch a case

Nedávno psal Jakub Vrána o tom, jak zjistit příslušnost prvku v seznamu. Zmínil se také o variantě Switch & Case, kterou lze nahradit sérii podmínek if.

switch ($a) {
case 'a':
case 'b':
case 'c':
case 'd':
default:
}

Tato konstrukce může být v určitých jazycích a za určitých situací nesmírně rychlá. Bohužel v PHP tomu tak není. V časově kritických rutinách se jí raději vyhněte (tedy této konstrukci, případně PHP vůbec).

Pro rekapitulaci, zjistit příslušnost prvku lze nejméně pěti způsoby:

If série

Nejpomalejší metoda, vhodná tehdy, pokud se „formát“ podmínky případ od případu liší (pokud se neopakuje jen $ch==...). Viz demo č. 1.

if ($ch=="\x00") { /* ... */ continue; }
if ($ch=="\x01") { /* ... */ continue; }
...

Switch & Case

Rychlejší a přehlednější varianta. Viz demo č. 2.

switch ($ch) {
case "\x00": /* ... */ break;
case "\x01": /* ... */ break;
...
}

in_array

Připravíme si pole hodnot a pak už jen testujeme (ne)přítomnost prvku v tomto poli. Viz demo č. 3.

if (in_array($ch, $arr)) {
  // ...
} else {
  // ...
}

array_key_exists

Opět pracuje nad polem, tentokrát se však netestuje přítomnost hodnot, alébrž klíčů (indexů). To je obecně velmi rychlé a tato metoda patří mezi nejrychlejší. Viz demo č. 4.

if (array_key_exists($ch, $arr)) {
  // ...
} else {
  // ...
}

isset

Zoptimalizovaná předchozí varianta. Využívá se jazykové konstrukce isset. Ta je rychlejší než funkce array_key_exists, narozdíl od ní však nedokáže zjistit přítomnost klíče NULL v poli. Viz demo č. 5.

if (isset($arr[$ch])) {
  // ...
} else {
  // ...
}

Srovnání

Rychlostní srovnání hovoří myslím jasně (větší hodnota znamená delší dobu zpracování).

Graf trvání jednotlivých benchmarků

Optimalizovat kód pro isset je obvykle velmi výhodné. A netýká se to jen polí, lze kupříkladu testovat i délku řetězce (isset($str{14}) namísto strlen($str)>14) atd. Ale to už hraničí s horší čitelností kódu.

Komentáře

  1. Arthur Dent #1

    Až když jsem to zkopíroval do tohodle okýnka (chtěl jsem ti napsat, že tam máš chybu), tak jsem si všiml, že v tom textu „isset($str{14})“ jsou ty závorky okolo čtrnáctky složené… Už je to se mnou vážný… Možná bys mohl udělat nějaké výraznější rozlišení mezi {} a () i v textu příspěvku, co ty na to? :)

    (embedded IE6 / Feedreader, WinXP)

    před 11 lety
  2. Dero #2

    avatar

    Díky, další článek – další pro mne zajímavá informace. Mám rád ty chvíle, kdy mi v čtečce svítí nepřečtená La Trine. :o)

    před 11 lety
  3. Andrew #3

    Zdá se mi to nebo tu někdo srovnává jablka, hrušky a švestky k tomu?

    Srovnání if a switch beru. Stejně tak in_array a isset. Ale nějak nedokážu pochopit proč je to všechno v jednom grafu a ještě s funkcí array_key_exists, vždyť se ty funkce používají k naprosto jiným věcem.

    Nebo mi snad někdo dokáže vysvětlit, jak to „pomalé switch a case“ nahradím tím ultrarychlým isset? Jinak navrhuju přidat do grafu třeba unset, mysql_connect, imacreatetruecolor a ještě pár dalších, ať to máme kompletní.

    před 11 lety | reagoval [5] David Grudl
  4. Jan Menšík #4

    avatar

    Prestoze funkce issset ci in_array samozrejme nedosahuji moznosti IF nebo SWITCH, ale chapu ze nekdy jdou pouzit a jsou rychlejsi.

    Domnivam se ale ze lepsi je misto silenych konstruktu psat hezky citelny kod. Lepe se s nim pracuje a tim padem se i lepe ladi. A treba zjistite ze danou testovanou podminku vubec nepotrebujete.

    Problem s rychlosti se vetsinou vyresi cacheovanim.

    před 11 lety | reagoval [5] David Grudl
  5. David Grudl http://davidgrudl.com #5

    avatar

    #3 Andrewe, je to reakce na odkazovaný článek, kde se tyto funkce vzájemně nahradit dají.

    Ale kdybych to vzal obecně, lze použít třeba toto nahrazení:

    for  ($i=strlen($str); $i; $i--)
    {
        $ch = $str{$i-1};
        switch ($ch) {
        case 'a': /* do something A */ break;
        case 'b': /* do something B */ break;
        case 'c': /* do something C */ break;
        default: /* default */
        }
    }

    za

    function doSomethingA() { ... }
    function doSomethingB() { ... }
    function doSomethingC() { ... }
    
    $arr  = array(
       'a' => 'doSomethingA',
       'b' => 'doSomethingB',
       'c' => 'doSomethingC',
    );
    
    for  ($i=strlen($str); $i; $i--)
    {
        $ch = $str{$i-1};
        if (isset($arr[$ch]))
            $arr[$ch]();
        else
            /* default */
    }

    Tedy jednotlivé volby case jsem nahradil funkcemi a ty jsou volány „přímo“, nikoliv po otestování neměnné série case. Při zpracování velkého řetězce lze očekávat dramatické zrychlení, jak naznačuje graf. Ten koneckonců slouží pouze pro vizuální představu, konkrétní hodnoty v něm neuvádím, protože by to bylo zavádějící.

    #4 Jane Menšíku, Na cacheování ne nespoléhejte, není všemocné. Kód sice urychlí, ale poměr rychlostí různých metod zůstane ± zachován.

    Souhlasím, že psát hezký a čitelný kód je potřeba (viz poslední věta článku), nicméně použití „rychlejší“ konstrukce přece neznamená psát nečitelněji.

    před 11 lety
  6. johno http://johno.jsmf.net #6

    avatar

    DGX: Zaujimalo by ma kolko krat si testy spustil a ci si to nejako potom spriemeroval. Taktiez standardnu odchylku by som si rad pozrel. Ak je totiz standardna odchylka priliz velka tak je ten graf minimalne zavadzajuci.

    před 11 lety | reagoval [7] David Grudl
  7. David Grudl http://davidgrudl.com #7

    avatar

    #6 johno, spustil jednou, zaokrouhlil na sekundy. Johno, nepíšu diplomovou práci – pokud máš zájem, zdrojáky jsou k dispozici a můžeš tomu věnovat hromadu času, provést hloubkové analýzy a nejspíš přijdeš na to stejné.

    před 11 lety | reagoval [8] johno [11] David Grudl
  8. johno #8

    avatar

    #7 Davide Grudle, Robit nejake zavery na zaklade jedneho spustenia mi prijde minimalne ako primitivne.

    před 11 lety | reagoval [9] David Grudl
  9. David Grudl http://davidgrudl.com #9

    avatar

    #8 johno, a když se jeden skript vykonává např. 30 sekund a druhý necelé dvě? Navíc v případě PHP4 mívám odchylky docela minimální (5%), pravda, u PHP5 to umí udělat i 20%.

    před 11 lety | reagoval [10] johno
  10. johno #10

    avatar

    #9 Davide Grudle, No prave taketo veci sa velmi lahko odhaluju, ked vidis standardnu odchylku. Ak je mala, tak to znamena, ze testy prebiehali stabilne bez nejakeho velkeho vonkajsieho vplyvu. Samotny priemer to nie je to prave orechove.

    Predstav si situaciu, ze pustas test 3×. Dva krat zbehne za 3 sekundy a raz za minutu. Prehanam, ale vyratajme si priemer.

    priemer = (3+3+60)/3 = 22sekund

    standardna odchylka = 26.87

    Hmm, nieco sa nam tu asi posralo.

    Zoberme teraz priklad pri testoch s casmi 2, 3, 3 sekund.

    priemer = 2.66
    odchylka = 0.47

    Toto uz vyzera na doveryhodnejsie (stabilnejsie) vysledky.

    Je mi jasne, ze ked je tam radovy rozdiel, tak standardna ochylka bude asi zbytocna, ale v tych ostatnych pripadoch by nemala chybat.

    před 11 lety
  11. David Grudl http://davidgrudl.com #11

    avatar

    Johno, máš zcela pravdu a tvůj komentář by si měl přečíst každý, kdo hodlá provádět nějaké měření.

    Samozřejmě, že jsem měřil poctivě, samotného mě výsledky zajímaly. Ale v reakci #7 David Grudl jsem Tě prostě odbyl, protože jsi po mě chtěl práci navíc (tedy měřit znovu, neboť odchylku ‚neskladuju‘), ačkoliv si ji můžeš udělat sám (zdrojáky jsou k dispozici).

    před 11 lety
  12. Marty http://www.martinvseticka.eu #12

    Benchmarky jsou jinde? Resp. odkazy na ně nefungují.

    před 9 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.