Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

OPcache の最適化器の今

7,798 views

Published on

PHP Conference 2017 での発表資料です。
PHP 7.1 から OPcache 内部の最適化器にデータフロー解析が実装され、より効率的なバイトコードが生成されるようになりました。過去と現在の OPcache の最適化器の処理について簡単に紹介したものです。

Published in: Technology

OPcache の最適化器の今

  1. 1. OPcache の最適化器の今 内山 雄司 (@y__uti) Japan PHP Conference 2017
  2. 2. 自己紹介 内山 雄司 (@y__uti) ◦ http://y-uti.hatenablog.jp/ (phpusers-ja) 仕事 ◦ 受託開発の会社 (株式会社ピコラボ) でプログラマをしています 興味 ◦ プログラミング言語処理系 ◦ 機械学習 2017-10-08 JAPAN PHP CONFERENCE 2017 2
  3. 3. ベンチマークテスト 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-3337U CentOS 7 (VM 上の Guest OS)
  4. 4. ベンチマークテスト 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-3337U CentOS 7 (VM 上の Guest OS) PHP 7 で高速化
  5. 5. ベンチマークテスト 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-3337U CentOS 7 (VM 上の Guest OS) OPcache 有効時 PHP 7.1 以降も速度向上
  6. 6. 発表の流れ ベンチマーク結果の紹介 PHP のプログラム実行と OPcache OPcache の最適化器の変遷 ◦ PHP 5.5 ~ 7.0 ◦ PHP 7.1 ◦ PHP 7.2 2017-10-08 JAPAN PHP CONFERENCE 2017 6
  7. 7. PHP プログラムの実行方式 バイトコードインタプリタ方式 2017-10-08 JAPAN PHP CONFERENCE 2017 7 PHP ファイルをコンパイルする インタプリタで実行する
  8. 8. プログラム実行の様子 プログラムをバイトコード命令列にコンパイルして逐次実行 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, 2 1 IS_SMALLER ~1 0, ~0 2 JMPZ ~1, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  9. 9. プログラム実行の様子 プログラムをバイトコード命令列にコンパイルして逐次実行 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, 2 1 IS_SMALLER ~1 0, ~0 2 JMPZ ~1, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  10. 10. プログラム実行の様子 プログラムをバイトコード命令列にコンパイルして逐次実行 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, 2 1 IS_SMALLER ~1 0, ~0 2 JMPZ ~1, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  11. 11. プログラム実行の様子 プログラムをバイトコード命令列にコンパイルして逐次実行 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, 2 1 IS_SMALLER ~1 0, ~0 2 JMPZ ~1, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  12. 12. プログラム実行の様子 プログラムをバイトコード命令列にコンパイルして逐次実行 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, 2 1 IS_SMALLER ~1 0, ~0 2 JMPZ ~1, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  13. 13. プログラム実行の様子 プログラムをバイトコード命令列にコンパイルして逐次実行 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, 2 1 IS_SMALLER ~1 0, ~0 2 JMPZ ~1, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  14. 14. プログラム実行の様子 プログラムをバイトコード命令列にコンパイルして逐次実行 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, 2 1 IS_SMALLER ~1 0, ~0 2 JMPZ ~1, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  15. 15. プログラム実行の様子 プログラムをバイトコード命令列にコンパイルして逐次実行 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, 2 1 IS_SMALLER ~1 0, ~0 2 JMPZ ~1, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  16. 16. OPcache の役割 バイトコード命令をキャッシュして再利用 2017-10-08 JAPAN PHP CONFERENCE 2017 16 バイトコードをキャッシュする PHP ファイルをコンパイルする キャッシュから バイトコードを取得する キャッシュ済み? インタプリタで実行する NO YES
  17. 17. OPcache の役割 バイトコード命令を最適化する 2017-10-08 JAPAN PHP CONFERENCE 2017 17 キャッシュから バイトコードを取得する キャッシュ済み? インタプリタで実行する NO YES バイトコードを最適化する バイトコードをキャッシュする PHP ファイルをコンパイルする
  18. 18. 発表の流れ ベンチマーク結果の紹介 PHP のプログラム実行と OPcache OPcache の最適化器の変遷 ◦ PHP 5.5 ~ 7.0 ◦ PHP 7.1 ◦ PHP 7.2 2017-10-08 JAPAN PHP CONFERENCE 2017 18
  19. 19. PHP 5.5 の最適化器 2017-10-08 JAPAN PHP CONFERENCE 2017 19 PASS 3 連続するジャンプの短絡など PASS 10 NOP 命令の除去 PASS 9 一時変数のメモリ領域共有 PASS 5 制御フローグラフを用いた最適化 PASS 2 条件分岐の置き換えなど PASS 1 定数式の置き換えなど
  20. 20. 素朴な最適化の例 出発点:最適化を行わない場合 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, 2 1 IS_SMALLER ~1 0, ~0 2 JMPZ ~1, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  21. 21. 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, 2 1 IS_SMALLER ~1 0, ~0 2 JMPZ ~1, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  22. 22. 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 NOP 1 NOP 2 JMPZ <bool>, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  23. 23. 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 NOP 1 NOP 2 JMPZ <bool>, ->8 3 JMPZ <bool>, ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  24. 24. 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 NOP 1 NOP 2 NOP 3 JMP ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  25. 25. 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 NOP 1 NOP 2 NOP 3 JMP ->6 4 ECHO 'A' 5 JMP ->7 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  26. 26. 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 NOP 1 NOP 2 NOP 3 JMP ->6 4 ECHO 'A' 5 JMP ->9 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  27. 27. 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 NOP 1 NOP 2 NOP 3 JMP ->6 4 ECHO 'A' 5 JMP ->9 6 ECHO 'B' 7 JMP ->9 8 ECHO 'C' 9 RETURN null  ソースコード  コンパイル結果
  28. 28. 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 ->3 1 ECHO 'A' 2 JMP ->6 3 ECHO 'B' 4 JMP ->6 5 ECHO 'C' 6 RETURN null  ソースコード  コンパイル結果
  29. 29. 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, 2 1 IS_SMALLER ~1 0, ~0 2 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'
  30. 30. 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, 2 1 IS_SMALLER ~1 0, ~0 2 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'
  31. 31. 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  ソースコード  コンパイル結果
  32. 32. 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
  33. 33. 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 関数スタックフレームの最適化
  34. 34. 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 関数のインライン化 (極めて限定的)
  35. 35. 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 ただし現時点では解析結果を積極的には利用していない様子
  36. 36. 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, 3 1 ADD ~2 !0, 3 2 ASSIGN !1, ~2 3 IS_SMALLER ~2 0, !1 4 JMPZ ~2, ->7 5 ASSIGN !0, 4 6 JMP ->9 7 MUL ~2 !0, 5.6 8 ASSIGN !1, ~2 9 ADD ~2, !0, !1 10 RETURN ~2
  37. 37. 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, !1 10 RETURN ~2 0 ASSIGN !0, 3 1 ADD ~2 !0, 3 2 ASSIGN !1, ~2 3 IS_SMALLER ~2 0, !1 4 JMPZ ~2, ->7 5 ASSIGN !0, 4 6 JMP ->9 7 MUL ~2 !0, 5.6 8 ASSIGN !1, ~2
  38. 38. 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;
  39. 39. 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;
  40. 40. 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;
  41. 41. 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) と呼ばれる
  42. 42. 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;
  43. 43. 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;
  44. 44. 解析結果の用途 型や値に応じた効率的なバイトコード命令への変換 2017-10-08 JAPAN PHP CONFERENCE 2017 44 function test():int { $a = 1; $a = $a + 1; return $a; } # op return operands ---------------------------------- 0 ASSIGN !0, 1 1 ASSIGN_ADD !0, 1 2 VERIFY_RETURN_TYPE !0 3 RETURN !0  ソースコード  コンパイル結果 (データフロー解析なし) 0 QM_ASSIGN !0 1 1 PRE_INC !0 2 RETURN !0  コンパイル結果 (データフロー解析あり) PHP 7.1 時点ではそれほど積極的な実装はされていない様子
  45. 45. 解析結果の用途 型に特化したハンドラの選択 "ADD 命令" は大量にある (データの種類や型によって "適切な ADD" を選択) 2017-10-08 JAPAN PHP CONFERENCE 2017 45 ZEND_ADD_SPEC_CONST_CONST_HANDLER ZEND_ADD_SPEC_CONST_TMPVAR_HANDLER ZEND_ADD_SPEC_CONST_CV_HANDLER ZEND_ADD_SPEC_TMPVAR_CONST_HANDLER ZEND_ADD_SPEC_TMPVAR_TMPVAR_HANDLER ZEND_ADD_SPEC_TMPVAR_CV_HANDLER ZEND_ADD_SPEC_CV_CONST_HANDLER ZEND_ADD_SPEC_CV_TMPVAR_HANDLER ZEND_ADD_SPEC_CV_CV_HANDLER ZEND_ADD_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV_HANDLER ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV_HANDLER ZEND_ADD_LONG_SPEC_CONST_TMPVARCV_HANDLER ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV_HANDLER ZEND_ADD_DOUBLE_SPEC_CONST_TMPVARCV_HANDLER ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV_HANDLER 7.1 で追加
  46. 46. 解析結果の用途 型に特化したハンドラの選択 "ADD 命令" は大量にある (データの種類や型によって "適切な ADD" を選択) 2017-10-08 JAPAN PHP CONFERENCE 2017 46 ZEND_ADD_SPEC_CONST_CONST_HANDLER ZEND_ADD_SPEC_CONST_TMPVAR_HANDLER ZEND_ADD_SPEC_CONST_CV_HANDLER ZEND_ADD_SPEC_TMPVAR_CONST_HANDLER ZEND_ADD_SPEC_TMPVAR_TMPVAR_HANDLER ZEND_ADD_SPEC_TMPVAR_CV_HANDLER ZEND_ADD_SPEC_CV_CONST_HANDLER ZEND_ADD_SPEC_CV_TMPVAR_HANDLER ZEND_ADD_SPEC_CV_CV_HANDLER ZEND_ADD_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV_HANDLER ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV_HANDLER ZEND_ADD_LONG_SPEC_CONST_TMPVARCV_HANDLER ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV_HANDLER ZEND_ADD_DOUBLE_SPEC_CONST_TMPVARCV_HANDLER ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV_HANDLER 7.1 で追加 比 較
  47. 47. 特化したハンドラの威力 ハンドラの違いによる実装の比較 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_OPLINE zval *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(); }
  48. 48. 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 不要コード除去
  49. 49. 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 3 ADD !1 3, !0 ADD !2 4, !1 ECHO !2 RETURN null  ソースコード  コンパイル結果 $a の右辺だけは計算されるが計算結果が後続の命令に伝わらない
  50. 50. 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 3 QM_ASSIGN !1 6 QM_ASSIGN !2 10 ECHO !2 RETURN null  ソースコード  コンパイル結果 PHP 7.2 で最適化レベルを指定 optimization_level=0x7fff8fff $b や $c の右辺もコンパイル時に計算され定数になる
  51. 51. 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 10 RETURN null  ソースコード  コンパイル結果 PHP 7.2 で最適化レベルを指定 optimization_level=0x7fffafff $a, $b, $c に代入する命令が除去され 10 が直接出力される
  52. 52. 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 10 RETURN null  ソースコード  コンパイル結果 PHP 7.2 のデフォルトの設定 optimization_level=0x7fffbfff スタックに $a, $b, $c の領域を確保しなくなる (PASS 12 の処理)
  53. 53. もう少し複雑な例 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; }
  54. 54. まだ出来ていないこと いろいろある 例:ループを含むコード 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 1 2 QM_ASSIGN !1 0 3 JMP ->5 4 PRE_INC !0 5 PRE_INC !1 6 IS_SMALLER ~2 !1, 3 7 JMPNZ ~2, ->3 8 ECHO !0 9 RETURN null  ソースコード  コンパイル結果
  55. 55. まとめ と 補足 PHP 7.1 以降で OPcache の最適化器が大きく進歩 コマンドラインからの PHP 実行で OPcache を有効にするには 2017-10-08 JAPAN PHP CONFERENCE 2017 55 $ php -d opcache.enable_cli=1 bench.php

×