php-srcのコードリーディングした内容をコツコツ残すテスト。その18
今日はopcacheによる最適化機構。 理解しやすかったpass1,3,10あたりから。
最適化は zend_optimize()
で行われている
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/zend_optimizer.c#L889:L889
pass 1 (Simple local optimizations)
zend_optimizer_pass1()
で最適化が行われる。コメントの通り以下の最適化を行う。
/* pass 1 (Simple local optimizations)
* - persistent constant substitution (true, false, null, etc)
* - constant casting (ADD expects numbers, CONCAT strings, etc)
* - constant expression evaluation
* - optimize constant conditional JMPs
* - pre-evaluate constant function calls
* - eliminate FETCH $GLOBALS followed by FETCH_DIM/UNSET_DIM/ISSET_ISEMPTY_DIM
*/
あらかじめstringにcastする(constant casting (ADD expects numbers, CONCAT strings, etc)
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass1.c#L48-L63
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass1.c#L106-L111
constの計算を予めやっておく(constant expression evaluation
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass1.c#L65-L104
zend_optimizer_eval_binary_op()
で get_binary_op()
を呼び出して演算子に応じた関数を取得・実行して計算する
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/zend_optimizer.c#L66-L67
例えば +
だと add_function()
が呼ばれる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_opcode.c#L1174:L1174
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_operators.c#L981-L1003
zend_optimizer_replace_by_const()
で TMP_VAR
として利用しているopcodeのオペランドも定数で置き換える
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass1.c#L95:L95
定数で置き換えできれば計算しているopcodeはNOPにできるし、できなければ ZEND_QM_ASSIGN
で定数をアサインするopcodeに変換している。
こちらは、あらかじめcastしている
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass1.c#L113-L130
zend_optimizer_eval_cast()
では型に応じた変換を行っている。
JMPZ, JMPNZの条件がconstであれば予め計算して必ずJMPする or JMPしない(NOP)ように( optimize constant conditional JMPs
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass1.c#L594-L614
strlenでCONSTだったら予め計算(pre-evaluate constant function calls
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass1.c#L484:L484
GLOBALSは if 0
になっていて通らない気がする?
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass1.c#L521
pass 3: (Jump optimization)
連続JMPを置き換え(/* convert JMP L1 ... L1: JMP L2 to JMP L2 .. L1: JMP L2 */
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass3.c#L72-L84
JMP先が次の命令であればそもそも何もせず次のオペコードにいければよいのでNOPに(/* convert L: JMP L+1 to NOP */
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass3.c#L86-L88
JMP => returnの場合はreturnに置き換える(/* JMP L, L: RETURN to immediate RETURN */
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass3.c#L102-L113
JMPZのJMP先が次になっていたらNOPでOK(どちらのパスもターゲットに行く。 /* JMPZ(X,L1), JMP(L1) -> NOP, JMP(L1) */
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/pass3.c#L117-L127
という感じでひたすらJMPの最適化を行う。
pass 10: remove NOPs
NOPを削除してズラす。JMP先のoffsetもズレるので良しなに変換する必要がある。
zend_optimizer_nop_removal()
が呼ばれる。
JMPしないルートが全部NOPであればJMPをNOP化
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/nop_removal.c#L47:L47
shift
はopcodeのシフト数で shiftlist
はopcodeのインデックスに対するシフト数
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/nop_removal.c#L60
NOP
があればズラすのでシフトする量も増えていく
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/nop_removal.c#L61-L62
new_count
はシフト後のopcodeの総数
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/nop_removal.c#L70
順番にずらしているのはこの部分
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/nop_removal.c#L64-L69
new_opline
に opline
の値をセットしている。
JMP系の命令は zend_optimizer_migrate_jump()
で新しいopcodeの位置から旧JMP先のアドレスの差分がセットされる。
新しいopcodeのlast(サイズ)を設定
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/nop_removal.c#L75
↓でJMP先のアドレスを変換
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/nop_removal.c#L79-L81
新しいopcodeの位置から旧JMP先のアドレスへの差分が入っているので、これを新しいJMP先のアドレスへの差分にする必要がある。
これはもとの値から shiftlist[index]
分のアドレスを差し引くことで実現している。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/Optimizer/zend_optimizer.c#L690-L693