blog.twista.cz :: PHP pod pokličkou - opcode 28.05.2017 - 04:44

web-developer & PHPLover
<< zpět na výpis

PHP pod pokličkou - opcode

qr kód tohoto článku

Při každém spuštění php scriptu ho interpret "prožene" parserem a pak ho zkompiluje do tzv. opcode (operation codes) které se následně spustí. Tento proces se děje vždy, pokud není použitá nějaká opcode cache (kdy se přeložený kód umístí do sdílené paměti aby se předešlo parsování a kompilování při každém spuštění). Pokud by jsme se chtěli podívat blíž jak opcode vlastně vypadá, pak seznam všech instrukcí najdeme v manuálu (http://php.net/manual/en/internals2.opcodes.php)



V tomto článku se podíváme jakým způsobem je dostat z php kódů a zkusíme je dle nich kód trochu zvizualizovat.

Pro překlad kódu do opcache využijeme Vulcan Logic Dumper od Dericka Rethanse, který nainstalujeme přes pecl, tedy:

pecl install vld

pokud vše doběhne, přidáme už jen řádek do php.ini

extension=vld.so

a teď hurá ke zkoušení, napsal jsem si krátký example s kterým budeme pracovat

function greet($name){
    echo 'hello '.$name;
}

for($i = 0; $i < 10; $i++){
    if($i < 5)
        greet($i);
}

nyní se podíváme jak by vypadal přeložený do opcode, pomocí nainstalovaného VLD

$ php -dvld.verbosity=0 -dvld.dump_paths=1 -dvld.active=1 file.php 
filename:       /mnt/web/www_my/performance/t1.php
function name:  (null)
number of ops:  20
compiled vars:  !0 = $i
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   3     0  >   EXT_STMT                                                 
         1      NOP                                                      
   7     2      EXT_STMT                                                 
         3      ASSIGN                                                   !0, 0
         4  >   IS_SMALLER                                       ~1      !0, 10
         5      EXT_STMT                                                 
         6    > JMPZNZ                                        A          ~1, ->19
         7  >   POST_INC                                         ~2      !0
         8      FREE                                                     ~2
         9    > JMP                                                      ->4
   8    10  >   EXT_STMT                                                 
        11      IS_SMALLER                                       ~3      !0, 5
   9    12    > JMPZ                                                     ~3, ->18
        13  >   EXT_FCALL_BEGIN                                          
        14      SEND_VAR                                                 !0
        15      DO_FCALL                                      1          'greet'
        16      EXT_FCALL_END                                            
        17    > JMP                                                      ->18
  10    18  > > JMP                                                      ->7
        19  > > RETURN                                                   1

branch: #  0; line:     3-    7; sop:     0; eop:     3; out1:   4
branch: #  4; line:     7-    7; sop:     4; eop:     6; out1:  19; out2:  10
branch: #  7; line:     7-    7; sop:     7; eop:     9; out1:   4
branch: # 10; line:     8-    9; sop:    10; eop:    12; out1:  13; out2:  18
branch: # 13; line:     9-    9; sop:    13; eop:    17; out1:  18
branch: # 18; line:    10-   10; sop:    18; eop:    18; out1:   7
branch: # 19; line:    10-   10; sop:    19; eop:    19
path #1: 0, 4, 19, 
path #2: 0, 4, 10, 13, 18, 7, 4, 19, 
path #3: 0, 4, 10, 18, 7, 4, 19, 
Function greet:
filename:       /mnt/web/www_my/performance/t1.php
function name:  greet
number of ops:  7
compiled vars:  !0 = $name
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   3     0  >   EXT_NOP                                                  
         1      RECV                                             !0      
   4     2      EXT_STMT                                                 
         3      CONCAT                                           ~0      'hello+', !0
         4      ECHO                                                     ~0
   5     5      EXT_STMT                                                 
         6    > RETURN                                                   null

branch: #  0; line:     3-    5; sop:     0; eop:     6
path #1: 0, 
End of function greet.

To vypadá na první přehled docela nepřehledně, nicméně po chvilce zkoumání přijdeme na to že v první části je kód for-cyklu, v druhé pak kód funkce. Pokud by byl nějaký kus kódu ve skriptu, kterého by bylo ne možné spustit, pak vld připíše za jeho pořadové číslo hvězdičku, ale na to určitě líp slouží jiné nástroje.

Nyní můžeme za pomoci knihovny graphviz vizualizovat celý skript. Pustíme znovu vld, teď ovšem s trošku jinými parametry.

$ php -dvld.dump_paths=1 -dvld.verbosity=0 -dvld.save_paths=1 -dvld.active=1 file.php

ten nám vygeneroval soubor /tmp/paths.dot který pustíme do graphviz-u

$ dot -Tpng /tmp/paths.dot > /tmp/paths.png

výsledek pak vidíme tady:

Pokud by jsme srovnali graf s kódem, pak dokážeme přesně popsat jak funguje. U takhle triviálního příkladu to není tolik zajímavé, než kdyby jsme to samé zkusily s větším kódem. Například tady graf jedné větší třídy.

Díky tomu že umíme získat opcodes, můžeme například také jednoduše zjistit, proč je isset() rychlejší než array_key_exists() (pokud neuvažujeme mírný rozdíl v chování obou funkcí).

Komentáře

comments powered by Disqus