Upload
y-uti
View
6.380
Download
0
Embed Size (px)
Citation preview
OPcache の最適化器の今内山 雄司 (@y__uti)
Japan PHP Conference 2017
自己紹介内山雄司 (@y__uti)
◦ http://y-uti.hatenablog.jp/ (phpusers-ja)
仕事◦ 受託開発の会社 (株式会社ピコラボ) でプログラマをしています
興味◦ プログラミング言語処理系
◦ 機械学習
2017-10-08 JAPAN PHP CONFERENCE 2017 2
ベンチマークテスト
0.0
0.5
1.0
1.5
2.0
2.5
3.0
5.5.38 5.6.31 7.0.24 7.1.10 7.2.0RC3
実行時間
[秒]
PHP バージョン
Zend/bench.php の実行時間 (各 5 回の実行の平均)
OPcache 無効 OPcache 有効
2017-10-08 JAPAN PHP CONFERENCE 2017 3
Core i5-3337UCentOS 7 (VM 上の Guest OS)
ベンチマークテスト
0.0
0.5
1.0
1.5
2.0
2.5
3.0
5.5.38 5.6.31 7.0.24 7.1.10 7.2.0RC3
実行時間
[秒]
PHP バージョン
Zend/bench.php の実行時間 (各 5 回の実行の平均)
OPcache 無効 OPcache 有効
2017-10-08 JAPAN PHP CONFERENCE 2017 4
Core i5-3337UCentOS 7 (VM 上の Guest OS)
PHP 7 で高速化
ベンチマークテスト
0.0
0.5
1.0
1.5
2.0
2.5
3.0
5.5.38 5.6.31 7.0.24 7.1.10 7.2.0RC3
実行時間
[秒]
PHP バージョン
Zend/bench.php の実行時間 (各 5 回の実行の平均)
OPcache 無効 OPcache 有効
2017-10-08 JAPAN PHP CONFERENCE 2017 5
Core i5-3337UCentOS 7 (VM 上の Guest OS)
OPcache 有効時PHP 7.1 以降も速度向上
発表の流れ
ベンチマーク結果の紹介
PHP のプログラム実行と OPcache
OPcache の最適化器の変遷
◦ PHP 5.5 ~ 7.0
◦ PHP 7.1
◦ PHP 7.2
2017-10-08 JAPAN PHP CONFERENCE 2017 6
PHP プログラムの実行方式バイトコードインタプリタ方式
2017-10-08 JAPAN PHP CONFERENCE 2017 7
PHP ファイルをコンパイルする
インタプリタで実行する
プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行
2017-10-08 JAPAN PHP CONFERENCE 2017 8
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行
2017-10-08 JAPAN PHP CONFERENCE 2017 9
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行
2017-10-08 JAPAN PHP CONFERENCE 2017 10
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行
2017-10-08 JAPAN PHP CONFERENCE 2017 11
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行
2017-10-08 JAPAN PHP CONFERENCE 2017 12
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行
2017-10-08 JAPAN PHP CONFERENCE 2017 13
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行
2017-10-08 JAPAN PHP CONFERENCE 2017 14
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行
2017-10-08 JAPAN PHP CONFERENCE 2017 15
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
OPcache の役割バイトコード命令をキャッシュして再利用
2017-10-08 JAPAN PHP CONFERENCE 2017 16
バイトコードをキャッシュする
PHP ファイルをコンパイルするキャッシュから
バイトコードを取得する
キャッシュ済み?
インタプリタで実行する
NO YES
OPcache の役割バイトコード命令を最適化する
2017-10-08 JAPAN PHP CONFERENCE 2017 17
キャッシュからバイトコードを取得する
キャッシュ済み?
インタプリタで実行する
NO YES
バイトコードを最適化する
バイトコードをキャッシュする
PHP ファイルをコンパイルする
発表の流れ
ベンチマーク結果の紹介
PHP のプログラム実行と OPcache
OPcache の最適化器の変遷
◦ PHP 5.5 ~ 7.0
◦ PHP 7.1
◦ PHP 7.2
2017-10-08 JAPAN PHP CONFERENCE 2017 18
PHP 5.5 の最適化器
2017-10-08 JAPAN PHP CONFERENCE 2017 19
PASS 3連続するジャンプの短絡など
PASS 10NOP 命令の除去
PASS 9一時変数のメモリ領域共有
PASS 5制御フローグラフを用いた最適化
PASS 2条件分岐の置き換えなど
PASS 1定数式の置き換えなど
素朴な最適化の例出発点:最適化を行わない場合
2017-10-08 JAPAN PHP CONFERENCE 2017 20
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
PASS 1 定数式の置き換え1 + 2 > 0 は true だと分かるので・・・
2017-10-08 JAPAN PHP CONFERENCE 2017 21
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
PASS 1 定数式の置き換えコンパイル時に計算してしまう
2017-10-08 JAPAN PHP CONFERENCE 2017 22
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 NOP1 NOP2 JMPZ <bool>, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
PASS 2 条件分岐の置き換えif 文の true/false はコンパイル時に決まっているので・・・
2017-10-08 JAPAN PHP CONFERENCE 2017 23
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 NOP1 NOP2 JMPZ <bool>, ->83 JMPZ <bool>, ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
PASS 2 条件分岐の置き換えNOP (飛ばない場合) か JMP (飛ぶ場合) に置き換える
2017-10-08 JAPAN PHP CONFERENCE 2017 24
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 NOP1 NOP2 NOP3 JMP ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
PASS 3 連続ジャンプの短絡JMP 命令の飛び先も JMP 命令なので・・・
2017-10-08 JAPAN PHP CONFERENCE 2017 25
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 NOP1 NOP2 NOP3 JMP ->64 ECHO 'A'5 JMP ->76 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
PASS 3 連続ジャンプの短絡最後の飛び先まで一気にジャンプする
2017-10-08 JAPAN PHP CONFERENCE 2017 26
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 NOP1 NOP2 NOP3 JMP ->64 ECHO 'A'5 JMP ->96 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
PASS 10 NOP 命令の除去NOP (何もしない命令) を実行しても何もしないのだから・・・
2017-10-08 JAPAN PHP CONFERENCE 2017 27
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 NOP1 NOP2 NOP3 JMP ->64 ECHO 'A'5 JMP ->96 ECHO 'B'7 JMP ->98 ECHO 'C'9 RETURN null
ソースコード コンパイル結果
PASS 10 NOP 命令の除去消す
2017-10-08 JAPAN PHP CONFERENCE 2017 28
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------
0 JMP ->31 ECHO 'A'2 JMP ->63 ECHO 'B'4 JMP ->65 ECHO 'C'6 RETURN null
ソースコード コンパイル結果
PASS 5 制御フローグラフ処理の流れをグラフ構造で表現して各種最適化を行う
2017-10-08 JAPAN PHP CONFERENCE 2017 29
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->8
ソースコード 制御フローグラフ
3 JMPZ <bool>, ->6
4 ECHO 'A'5 JMP ->7
6 ECHO 'B'
7 JMP ->9
9 RETURN null
8 ECHO 'C'
PASS 5 制御フローグラフこの例ではどこを通るか決まっているので・・・
2017-10-08 JAPAN PHP CONFERENCE 2017 30
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
0 ADD ~0 1, 21 IS_SMALLER ~1 0, ~02 JMPZ ~1, ->8
ソースコード 制御フローグラフ
3 JMPZ <bool>, ->6
4 ECHO 'A'5 JMP ->7
6 ECHO 'B'
7 JMP ->9
9 RETURN null
8 ECHO 'C'
PASS 5 制御フローグラフここまで最適化される (PHP 5.5 の頃からこの程度はできていた)
2017-10-08 JAPAN PHP CONFERENCE 2017 31
function test(){if (1 + 2 > 0) {if (false) {echo "A";
} else {echo "B";
}} else {echo "C";
}}
# op return operands----------------------------------0 ECHO 'B'1 RETURN null
ソースコード コンパイル結果
PHP 5.6 の最適化器
2017-10-08 JAPAN PHP CONFERENCE 2017 32
PASS 4関数呼び出しの効率化
PASS 11未使用リテラルの除去
PASS 9
PASS 5PASS 1
PASS 2
PASS 3 PASS 10
PHP 7.0 の最適化器
2017-10-08 JAPAN PHP CONFERENCE 2017 33
PASS 9
PASS 5PASS 1
PASS 2
PASS 3 PASS 10
PASS 4 PASS 11
PASS 12関数スタックフレームの最適化
PHP 7.1 の最適化器
2017-10-08 JAPAN PHP CONFERENCE 2017 34
PASS 9
PASS 1
PASS 2
PASS 3 PASS 10
PASS 11
PASS 5
PASS 6データフロー解析に基づく最適化
PASS 12
PASS 7コールグラフの構築
PASS 4 (関数呼び出しの効率化)
→ PASS 16関数のインライン化 (極めて限定的)
PASS 7 コールグラフ関数間の呼び出し関係をグラフ構造で表現して各種最適化を行う
2017-10-08 JAPAN PHP CONFERENCE 2017 35
function test() {return f();
}
function f() {return x() + y();
}
function x() { ... }function y() { ... }
ソースコード コールグラフ
f
test
x y
ただし現時点では解析結果を積極的には利用していない様子
PASS 6 データフロー解析プログラムの各点でデータが取り得る型や値を解析する
2017-10-08 JAPAN PHP CONFERENCE 2017 36
function test(){$a = 1 + 2;$b = $a + 3;if ($b > 0) {$a = 4;
} else {$b = $a * 5.6;
}return $a + $b;
}
ソースコード バイトコード命令列 (データフロー解析前)
0 ASSIGN !0, 31 ADD ~2 !0, 32 ASSIGN !1, ~23 IS_SMALLER ~2 0, !14 JMPZ ~2, ->75 ASSIGN !0, 46 JMP ->97 MUL ~2 !0, 5.68 ASSIGN !1, ~29 ADD ~2, !0, !110 RETURN ~2
PASS 6 データフロー解析プログラムの各点でデータが取り得る型や値を解析する
2017-10-08 JAPAN PHP CONFERENCE 2017 37
function test(){$a = 1 + 2;$b = $a + 3;if ($b > 0) {$a = 4;
} else {$b = $a * 5.6;
}return $a + $b;
}
ソースコード 制御フローグラフ
9 ADD ~2, !0, !110 RETURN ~2
0 ASSIGN !0, 31 ADD ~2 !0, 32 ASSIGN !1, ~23 IS_SMALLER ~2 0, !14 JMPZ ~2, ->7
5 ASSIGN !0, 46 JMP ->9
7 MUL ~2 !0, 5.68 ASSIGN !1, ~2
PASS 6 データフロー解析プログラムの各点でデータが取り得る型や値を解析する
2017-10-08 JAPAN PHP CONFERENCE 2017 38
function test(){$a = 1 + 2;$b = $a + 3;if ($b > 0) {$a = 4;
} else {$b = $a * 5.6;
}return $a + $b;
}
ソースコード 制御フローグラフ
return $a + $b;
$a = 3;
$b = $a + 3;
if ($b > 0)
$a = 4; $b = $a * 5.6;
PASS 6 データフロー解析プログラムの各点でデータが取り得る型や値を解析する
2017-10-08 JAPAN PHP CONFERENCE 2017 39
function test(){$a = 1 + 2;$b = $a + 3;if ($b > 0) {$a = 4;
} else {$b = $a * 5.6;
}return $a + $b;
}
ソースコード 制御フローグラフ
return $a + $b;
$a = 3;
$b = $a + 3;
if ($b > 0)
$a = 4; $b = $a * 5.6;
PASS 6 データフロー解析プログラムの各点でデータが取り得る型や値を解析する
2017-10-08 JAPAN PHP CONFERENCE 2017 40
function test(){$a = 1 + 2;$b = $a + 3;if ($b > 0) {$a = 4;
} else {$b = $a * 5.6;
}return $a + $b;
}
ソースコード 制御フローグラフ
return $a + $b;
$a = 3;
$b = $a + 3;
if ($b > 0)
$a = 4; $b = $a * 5.6;
PASS 6 データフロー解析プログラムの各点でデータが取り得る型や値を解析する
2017-10-08 JAPAN PHP CONFERENCE 2017 41
function test(){$a = 1 + 2;$b = $a + 3;if ($b > 0) {$a = 4;
} else {$b = $a * 5.6;
}return $a + $b;
}
ソースコード 制御フローグラフ
$a <- $a or $a;return $a + $b;
$a = 3;
$b = $a + 3;
if ($b > 0)
$a = 4; $b = $a * 5.6;
静的単一代入形式 (SSA) と呼ばれる
PASS 6 データフロー解析プログラムの各点でデータが取り得る型や値を解析する
2017-10-08 JAPAN PHP CONFERENCE 2017 42
function test(){$a = 1 + 2;$b = $a + 3;if ($b > 0) {$a = 4;
} else {$b = $a * 5.6;
}return $a + $b;
}
ソースコード 制御フローグラフ
$a <- $a or $a;$b <- $b or $b;return $a + $b;
$a = 3;
$b = $a + 3;
if ($b > 0)
$a = 4; $b = $a * 5.6;
PASS 6 データフロー解析プログラムの各点でデータが取り得る型や値を解析する
2017-10-08 JAPAN PHP CONFERENCE 2017 43
function test(){$a = 1 + 2;$b = $a + 3;if ($b > 0) {$a = 4;
} else {$b = $a * 5.6;
}return $a + $b;
}
ソースコード 制御フローグラフ
$a <- $a or $a; // [3, 4]: int$b <- $b or $b; // ?: [int, float]return $a + $b;
$a = 3; // 3: int
$b = $a + 3; // 6: int
if ($b > 0)
// 4: int$a = 4;
// ?: float$b = $a * 5.6;
解析結果の用途型や値に応じた効率的なバイトコード命令への変換
2017-10-08 JAPAN PHP CONFERENCE 2017 44
function test():int{$a = 1;$a = $a + 1;return $a;
}
# op return operands----------------------------------0 ASSIGN !0, 11 ASSIGN_ADD !0, 12 VERIFY_RETURN_TYPE !03 RETURN !0
ソースコード コンパイル結果 (データフロー解析なし)
0 QM_ASSIGN !0 11 PRE_INC !02 RETURN !0
コンパイル結果 (データフロー解析あり)
PHP 7.1 時点ではそれほど積極的な実装はされていない様子
解析結果の用途型に特化したハンドラの選択
"ADD 命令" は大量にある (データの種類や型によって "適切な ADD" を選択)
2017-10-08 JAPAN PHP CONFERENCE 2017 45
ZEND_ADD_SPEC_CONST_CONST_HANDLERZEND_ADD_SPEC_CONST_TMPVAR_HANDLERZEND_ADD_SPEC_CONST_CV_HANDLERZEND_ADD_SPEC_TMPVAR_CONST_HANDLERZEND_ADD_SPEC_TMPVAR_TMPVAR_HANDLERZEND_ADD_SPEC_TMPVAR_CV_HANDLERZEND_ADD_SPEC_CV_CONST_HANDLERZEND_ADD_SPEC_CV_TMPVAR_HANDLERZEND_ADD_SPEC_CV_CV_HANDLERZEND_ADD_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV_HANDLERZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV_HANDLERZEND_ADD_LONG_SPEC_CONST_TMPVARCV_HANDLERZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV_HANDLERZEND_ADD_DOUBLE_SPEC_CONST_TMPVARCV_HANDLERZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV_HANDLER
7.1 で追加
解析結果の用途型に特化したハンドラの選択
"ADD 命令" は大量にある (データの種類や型によって "適切な ADD" を選択)
2017-10-08 JAPAN PHP CONFERENCE 2017 46
ZEND_ADD_SPEC_CONST_CONST_HANDLERZEND_ADD_SPEC_CONST_TMPVAR_HANDLERZEND_ADD_SPEC_CONST_CV_HANDLERZEND_ADD_SPEC_TMPVAR_CONST_HANDLERZEND_ADD_SPEC_TMPVAR_TMPVAR_HANDLERZEND_ADD_SPEC_TMPVAR_CV_HANDLERZEND_ADD_SPEC_CV_CONST_HANDLERZEND_ADD_SPEC_CV_TMPVAR_HANDLERZEND_ADD_SPEC_CV_CV_HANDLERZEND_ADD_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV_HANDLERZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV_HANDLERZEND_ADD_LONG_SPEC_CONST_TMPVARCV_HANDLERZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV_HANDLERZEND_ADD_DOUBLE_SPEC_CONST_TMPVARCV_HANDLERZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV_HANDLER
7.1 で追加
比較
特化したハンドラの威力ハンドラの違いによる実装の比較
2017-10-08 JAPAN PHP CONFERENCE 2017 47
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_SPEC_CONST_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS){
USE_OPLINE
zval *op1, *op2, *result;
op1 = EX_CONSTANT(opline->op1);op2 = _get_zval_ptr_cv_undef(execute_data, opline->op2.var);if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_LONG)) {
if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_LONG)) {result = EX_VAR(opline->result.var);fast_long_add_function(result, op1, op2);ZEND_VM_NEXT_OPCODE();
} else if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_DOUBLE)) {result = EX_VAR(opline->result.var);ZVAL_DOUBLE(result, ((double)Z_LVAL_P(op1)) + Z_DVAL_P(op2));ZEND_VM_NEXT_OPCODE();
}} else if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_DOUBLE)) {
if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_DOUBLE)) {result = EX_VAR(opline->result.var);ZVAL_DOUBLE(result, Z_DVAL_P(op1) + Z_DVAL_P(op2));ZEND_VM_NEXT_OPCODE();
} else if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_LONG)) {result = EX_VAR(opline->result.var);ZVAL_DOUBLE(result, Z_DVAL_P(op1) + ((double)Z_LVAL_P(op2)));ZEND_VM_NEXT_OPCODE();
}}
SAVE_OPLINE();if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(op1) == IS_UNDEF)) {
op1 = GET_OP1_UNDEF_CV(op1, BP_VAR_R);}if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(op2) == IS_UNDEF)) {
op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R);}add_function(EX_VAR(opline->result.var), op1, op2);
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();}
従来のハンドラ (CONST_CV) 特化ハンドラ (LONG_NO_OVERFLOW)
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV_HANDLER(ZEND_OPCODE_HANDLER_ARGS){
USE_OPLINEzval *op1, *op2, *result;
op1 = EX_CONSTANT(opline->op1);op2 = EX_VAR(opline->op2.var);result = EX_VAR(opline->result.var);ZVAL_LONG(result, Z_LVAL_P(op1) + Z_LVAL_P(op2));ZEND_VM_NEXT_OPCODE();
}
PHP 7.2 の最適化器
2017-10-08 JAPAN PHP CONFERENCE 2017 48
PASS 9
PASS 1
PASS 2
PASS 3 PASS 10
PASS 4→ PASS 16
PASS 11
PASS 5
PASS 13未使用変数の除去
PASS 12
PASS 6 (データフロー解析)
PASS 7 (コールグラフの構築)
→ PASS 8定数伝播
→ PASS 14不要コード除去
PASS 8 定数伝播定数式をコンパイル時に計算する
例:PHP 7.1 までは・・・
2017-10-08 JAPAN PHP CONFERENCE 2017 49
function test(){$a = 1 + 2;$b = $a + 3;$c = $b + 4;echo $c;
}
compiled vars: !0=$a, !1=$b, !2=$c
op return operands-------------------------------------QM_ASSIGN !0 3ADD !1 3, !0ADD !2 4, !1ECHO !2RETURN null
ソースコード コンパイル結果
$a の右辺だけは計算されるが計算結果が後続の命令に伝わらない
PASS 8 定数伝播定数式をコンパイル時に計算する
例:定数伝播を適用すると
2017-10-08 JAPAN PHP CONFERENCE 2017 50
function test(){$a = 1 + 2;$b = $a + 3;$c = $b + 4;echo $c;
}
compiled vars: !0=$a, !1=$b, !2=$c
op return operands-------------------------------------QM_ASSIGN !0 3QM_ASSIGN !1 6QM_ASSIGN !2 10ECHO !2RETURN null
ソースコード コンパイル結果
PHP 7.2 で最適化レベルを指定
optimization_level=0x7fff8fff
$b や $c の右辺もコンパイル時に計算され定数になる
PASS 14 不要コードの除去不要なバイトコード命令を除去する
例:不要コード除去も適用すると
2017-10-08 JAPAN PHP CONFERENCE 2017 51
function test(){$a = 1 + 2;$b = $a + 3;$c = $b + 4;echo $c;
}
compiled vars: !0=$a, !1=$b, !2=$c
op return operands-------------------------------------ECHO 10RETURN null
ソースコード コンパイル結果
PHP 7.2 で最適化レベルを指定
optimization_level=0x7fffafff
$a, $b, $c に代入する命令が除去され 10 が直接出力される
PASS 13 未使用変数の除去使われることのない変数を除去する
例:未使用変数の除去も適用すると
2017-10-08 JAPAN PHP CONFERENCE 2017 52
function test(){$a = 1 + 2;$b = $a + 3;$c = $b + 4;echo $c;
}
compiled vars: none
op return operands-------------------------------------ECHO 10RETURN null
ソースコード コンパイル結果
PHP 7.2 のデフォルトの設定
optimization_level=0x7fffbfff
スタックに $a, $b, $c の領域を確保しなくなる (PASS 12 の処理)
もう少し複雑な例
2017-10-08 JAPAN PHP CONFERENCE 2017 53
# op return operands----------------------------------0 RETURN 10
ソースコード コンパイル結果
function test() {$a = 1 + 2;$b = $a + 3;if ($b > 0) {$a = 4;
} else {$b = $a * 5.6;
}return $a + $b;
}
まだ出来ていないこといろいろある
例:ループを含むコード
2017-10-08 JAPAN PHP CONFERENCE 2017 54
function test(){$a = 1;for ($i=0; $i<3; ++$i) {++$a;
}echo $a;
}
# op return operands------------------------------1 QM_ASSIGN !0 12 QM_ASSIGN !1 03 JMP ->54 PRE_INC !05 PRE_INC !16 IS_SMALLER ~2 !1, 37 JMPNZ ~2, ->38 ECHO !09 RETURN null
ソースコード コンパイル結果
まとめと補足PHP 7.1 以降で OPcache の最適化器が大きく進歩
コマンドラインからの PHP 実行で OPcache を有効にするには
2017-10-08 JAPAN PHP CONFERENCE 2017 55
$ php -d opcache.enable_cli=1 bench.php