2022-05-03

[php-src読書録]その19: opcacheの最適化処理

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_oplineopline の値をセットしている。

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

このエントリーをはてなブックマークに追加