55
OPcache の最適化器の今 内山 雄司 (@y__uti) Japan PHP Conference 2017

OPcache の最適化器の今

  • Upload
    y-uti

  • View
    6.380

  • Download
    0

Embed Size (px)

Citation preview

Page 1: OPcache の最適化器の今

OPcache の最適化器の今内山 雄司 (@y__uti)

Japan PHP Conference 2017

Page 2: OPcache の最適化器の今

自己紹介内山雄司 (@y__uti)

◦ http://y-uti.hatenablog.jp/ (phpusers-ja)

仕事◦ 受託開発の会社 (株式会社ピコラボ) でプログラマをしています

興味◦ プログラミング言語処理系

◦ 機械学習

2017-10-08 JAPAN PHP CONFERENCE 2017 2

Page 3: OPcache の最適化器の今

ベンチマークテスト

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)

Page 4: OPcache の最適化器の今

ベンチマークテスト

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 で高速化

Page 5: OPcache の最適化器の今

ベンチマークテスト

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 以降も速度向上

Page 6: OPcache の最適化器の今

発表の流れ

ベンチマーク結果の紹介

PHP のプログラム実行と OPcache

OPcache の最適化器の変遷

◦ PHP 5.5 ~ 7.0

◦ PHP 7.1

◦ PHP 7.2

2017-10-08 JAPAN PHP CONFERENCE 2017 6

Page 7: OPcache の最適化器の今

PHP プログラムの実行方式バイトコードインタプリタ方式

2017-10-08 JAPAN PHP CONFERENCE 2017 7

PHP ファイルをコンパイルする

インタプリタで実行する

Page 8: OPcache の最適化器の今

プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行

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

ソースコード コンパイル結果

Page 9: OPcache の最適化器の今

プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行

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

ソースコード コンパイル結果

Page 10: OPcache の最適化器の今

プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行

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

ソースコード コンパイル結果

Page 11: OPcache の最適化器の今

プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行

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

ソースコード コンパイル結果

Page 12: OPcache の最適化器の今

プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行

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

ソースコード コンパイル結果

Page 13: OPcache の最適化器の今

プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行

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

ソースコード コンパイル結果

Page 14: OPcache の最適化器の今

プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行

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

ソースコード コンパイル結果

Page 15: OPcache の最適化器の今

プログラム実行の様子プログラムをバイトコード命令列にコンパイルして逐次実行

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

ソースコード コンパイル結果

Page 16: OPcache の最適化器の今

OPcache の役割バイトコード命令をキャッシュして再利用

2017-10-08 JAPAN PHP CONFERENCE 2017 16

バイトコードをキャッシュする

PHP ファイルをコンパイルするキャッシュから

バイトコードを取得する

キャッシュ済み?

インタプリタで実行する

NO YES

Page 17: OPcache の最適化器の今

OPcache の役割バイトコード命令を最適化する

2017-10-08 JAPAN PHP CONFERENCE 2017 17

キャッシュからバイトコードを取得する

キャッシュ済み?

インタプリタで実行する

NO YES

バイトコードを最適化する

バイトコードをキャッシュする

PHP ファイルをコンパイルする

Page 18: OPcache の最適化器の今

発表の流れ

ベンチマーク結果の紹介

PHP のプログラム実行と OPcache

OPcache の最適化器の変遷

◦ PHP 5.5 ~ 7.0

◦ PHP 7.1

◦ PHP 7.2

2017-10-08 JAPAN PHP CONFERENCE 2017 18

Page 19: OPcache の最適化器の今

PHP 5.5 の最適化器

2017-10-08 JAPAN PHP CONFERENCE 2017 19

PASS 3連続するジャンプの短絡など

PASS 10NOP 命令の除去

PASS 9一時変数のメモリ領域共有

PASS 5制御フローグラフを用いた最適化

PASS 2条件分岐の置き換えなど

PASS 1定数式の置き換えなど

Page 20: OPcache の最適化器の今

素朴な最適化の例出発点:最適化を行わない場合

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

ソースコード コンパイル結果

Page 21: OPcache の最適化器の今

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

ソースコード コンパイル結果

Page 22: OPcache の最適化器の今

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

ソースコード コンパイル結果

Page 23: OPcache の最適化器の今

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

ソースコード コンパイル結果

Page 24: OPcache の最適化器の今

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

ソースコード コンパイル結果

Page 25: OPcache の最適化器の今

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

ソースコード コンパイル結果

Page 26: OPcache の最適化器の今

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

ソースコード コンパイル結果

Page 27: OPcache の最適化器の今

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

ソースコード コンパイル結果

Page 28: OPcache の最適化器の今

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

ソースコード コンパイル結果

Page 29: OPcache の最適化器の今

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'

Page 30: OPcache の最適化器の今

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'

Page 31: OPcache の最適化器の今

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

ソースコード コンパイル結果

Page 32: OPcache の最適化器の今

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

Page 33: OPcache の最適化器の今

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関数スタックフレームの最適化

Page 34: OPcache の最適化器の今

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関数のインライン化 (極めて限定的)

Page 35: OPcache の最適化器の今

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

ただし現時点では解析結果を積極的には利用していない様子

Page 36: OPcache の最適化器の今

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

Page 37: OPcache の最適化器の今

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

Page 38: OPcache の最適化器の今

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;

Page 39: OPcache の最適化器の今

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;

Page 40: OPcache の最適化器の今

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;

Page 41: OPcache の最適化器の今

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) と呼ばれる

Page 42: OPcache の最適化器の今

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;

Page 43: OPcache の最適化器の今

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;

Page 44: OPcache の最適化器の今

解析結果の用途型や値に応じた効率的なバイトコード命令への変換

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 時点ではそれほど積極的な実装はされていない様子

Page 45: OPcache の最適化器の今

解析結果の用途型に特化したハンドラの選択

"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 で追加

Page 46: OPcache の最適化器の今

解析結果の用途型に特化したハンドラの選択

"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 で追加

比較

Page 47: OPcache の最適化器の今

特化したハンドラの威力ハンドラの違いによる実装の比較

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();

}

Page 48: OPcache の最適化器の今

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不要コード除去

Page 49: OPcache の最適化器の今

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 の右辺だけは計算されるが計算結果が後続の命令に伝わらない

Page 50: OPcache の最適化器の今

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 の右辺もコンパイル時に計算され定数になる

Page 51: OPcache の最適化器の今

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 が直接出力される

Page 52: OPcache の最適化器の今

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 の処理)

Page 53: OPcache の最適化器の今

もう少し複雑な例

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;

}

Page 54: OPcache の最適化器の今

まだ出来ていないこといろいろある

例:ループを含むコード

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

ソースコード コンパイル結果

Page 55: OPcache の最適化器の今

まとめと補足PHP 7.1 以降で OPcache の最適化器が大きく進歩

コマンドラインからの PHP 実行で OPcache を有効にするには

2017-10-08 JAPAN PHP CONFERENCE 2017 55

$ php -d opcache.enable_cli=1 bench.php