Slide 1

Slide 1 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 1/78 PHPの関数実行とその計測 PHPの関数実行とその計測 五十嵐 進士 / @sji_ch

Slide 2

Slide 2 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 2/78 私は誰ですか 私は誰ですか サーバサイドプログラマー 株式会社インフィニットループ たぶん月刊 PHP ニュース php-master-changes

Slide 3

Slide 3 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 3/78 仙台民、学生時代は佐世保民 PHP カンファレンス仙台とかやりました PHP 歴は累計 6 年くらい 元は趣味で C 言語を少し

Slide 4

Slide 4 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 4/78

Slide 5

Slide 5 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 5/78 Agenda Agenda PHP の関数実行の仕組みについて PHP の関数実行の計測について

Slide 6

Slide 6 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 6/78 PHP の関数とは PHP の関数とは

Slide 7

Slide 7 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 7/78 プログラムに行わせる一連の処理のまとまり

Slide 8

Slide 8 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 8/78

Slide 9

Slide 9 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 9/78 PHP の関数実行の仕組み PHP の関数実行の仕組み

Slide 10

Slide 10 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 10/78 ZendEngine の概要 ZendEngine の概要 ZendEngine って知ってる? ZendEngine が何のことか大体分かる?

Slide 11

Slide 11 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 11/78 PHP 公式処理系の中心部(エンジン) PHP 4 の頃に Zeev さんと Andi さんが作った 2 字ずつとって Zend

Slide 12

Slide 12 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 12/78 ZendEngine = バイトコードコンパイラ + バイトコードを実行する VM

Slide 13

Slide 13 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 13/78 PHP コードは ZendVM のオペコードにコンパイルされる ZendVM(Executor) がオペコードを実行する

Slide 14

Slide 14 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 14/78

Slide 15

Slide 15 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 15/78

Slide 16

Slide 16 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 16/78 // 実際にはもう少し複雑 foreach ($op_array as $opline) { // 例: [ADD, [1, 2]] [$opcode, $operands] = $opline; $handler = $opcode_handlers[$opcode]; $handler($operands); }

Slide 17

Slide 17 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 17/78 コンパイルの流れ コンパイルの流れ

Slide 18

Slide 18 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 18/78

Slide 19

Slide 19 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 19/78 字句解析 字句解析 ↓

Slide 20

Slide 20 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 20/78 構文解析 構文解析

Slide 21

Slide 21 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 21/78 構文解析(構文規則一部) 構文解析(構文規則一部) top_statement: function_declaration_statement function_declaration_statement: function returns_ref T_STRING '(' parameter_list ')' return_type '{' inner_statement_list '}' returns_ref: /* empty */ | '&' parameter_list: non_empty_parameter_list | /* empty */ non_empty_parameter_list: parameter | non_empty_parameter_list ',' parameter

Slide 22

Slide 22 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 22/78 構文解析(構文規則一部) 構文解析(構文規則一部) parameter: optional_type is_reference is_variadic T_VARIABLE | optional_type is_reference is_variadic T_VARIABLE '=' expr optional_type: /* empty */ | type_expr type_expr: type | '?' type type: T_ARRAY | T_CALLABLE | name

Slide 23

Slide 23 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 23/78 構文解析(構文規則一部) 構文解析(構文規則一部) name: namespace_name | T_NAMESPACE T_NS_SEPARATOR namespace_name | T_NS_SEPARATOR namespace_name namespace_name: T_STRING | namespace_name T_NS_SEPARATOR T_STRING return_type: /* empty */ | ':' type_expr ;

Slide 24

Slide 24 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 24/78 構文解析(構文木) 構文解析(構文木) ↑(先の字句解析結果)を構文規則に当てはめると

Slide 25

Slide 25 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 25/78 構文解析(具象構文木) 構文解析(具象構文木)

Slide 26

Slide 26 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 26/78 構文解析(具象構文木) 構文解析(具象構文木) top_statement_list top_statement function_declaration_statement statement function return_type T_FUNCTION : T_STRING parameter_list ( ) nom_empty_parameter_list parameter parameter , optional_type T_VARIABLE type_expr type name namespace_name T_STRING optional_type T_VARIABLE type_expr type name namespace_name T_STRING type name namespace_name type_expr T_STRING inner_statement_list この先省略 この先省略 top_statement

Slide 27

Slide 27 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 27/78 構文解析(AST) 構文解析(AST) AST_STMT_LIST AST_FUNC_DECL AST_ECHO AST_PARAM_LIST AST_STMT_LIST AST_TYPE AST_PARAM AST_PARAM AST_TYPE a AST_TYPE b AST_RETURN AST_BINARY_OP AST_VAR AST_VAR a b AST_CALL AST_NAME AST_ARG_LIST 1 2

Slide 28

Slide 28 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 28/78 コード生成 コード生成

Slide 29

Slide 29 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 29/78 AST_STMT_LIST AST_FUNC_DECL AST_PARAM_LIST AST_STMT_LIST AST_TYPE AST_PARAM AST_PARAM AST_TYPE a AST_TYPE b AST_RETURN AST_BINARY_OP AST_VAR AST_VAR a b

Slide 30

Slide 30 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 30/78 AST をオペコードにコンパイル AST をオペコードにコンパイル

Slide 31

Slide 31 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 31/78 生成されるコード 生成されるコード $_main: INIT_FCALL 2 128 string("sum") SEND_VAL int(1) 1 SEND_VAL int(2) 2 V0 = DO_UCALL ECHO V0 RETURN int(1) sum: CV0($a) = RECV 1 CV1($b) = RECV 2 T2 = ADD CV0($a) CV1($b) VERIFY_RETURN_TYPE T2 RETURN T2 VERIFY_RETURN_TYPE RETURN null

Slide 32

Slide 32 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 32/78 関数 と op_array 関数 と op_array

Slide 33

Slide 33 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 33/78

Slide 34

Slide 34 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 34/78

Slide 35

Slide 35 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 35/78 関数呼び出しのオペコード 関数呼び出しのオペコード $_main: INIT_FCALL 2 128 string("sum") SEND_VAL int(1) 1 SEND_VAL int(2) 2 V0 = DO_UCALL ECHO V0 RETURN int(1) sum: CV0($a) = RECV 1 CV1($b) = RECV 2 T2 = ADD CV0($a) CV1($b) VERIFY_RETURN_TYPE T2 RETURN T2 VERIFY_RETURN_TYPE RETURN null

Slide 36

Slide 36 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 36/78 VMのスタック VMのスタック

Slide 37

Slide 37 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 37/78 ファイルの読み込み ファイルの読み込み

Slide 38

Slide 38 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 38/78 ここ見ると大抵わかる ここ見ると大抵わかる

Slide 39

Slide 39 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 39/78 処理系の標準処理のフック 処理系の標準処理のフック

Slide 40

Slide 40 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 40/78 Zend Engine の一部内部処理は関数ポインタ zend_compile zend_execute zend_ast_process zend_set_user_opcode_handler 拡張等から色々差し替え可能

Slide 41

Slide 41 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 41/78 ZendEngineでの関数実行まとめ ZendEngineでの関数実行まとめ PHP コードは関数の集まり、一見ないように見えても 関数はオペコードの集まり PHP コードはオペコードにコンパイルされる VM はメモリ上にその時々の状態を持つ ZendEngine の挙動は拡張等でカスタマイズできる

Slide 42

Slide 42 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 42/78 関数実行の計測 関数実行の計測

Slide 43

Slide 43 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 43/78 プログラムの高速化 とりあえず何をするか

Slide 44

Slide 44 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 44/78 推測するな、計測せよ

Slide 45

Slide 45 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 45/78 ボトルネック ボトルネック 実行時間の中の大きい割合を占める部分 経験則として、わりと一部コードが原因で全体の足を引っ張りがち ボトルネックを集中的に改善すると効率が良い

Slide 46

Slide 46 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 46/78 原始的計測 原始的計測

Slide 47

Slide 47 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 47/78 原始的方法の良いところ 原始的方法の良いところ 追加のライブラリやツールのインストールがいらない 任意の区間を計測対象に出来る DB/キャッシュアクセスの共通コードに仕込む等で大枠はつかめる

Slide 48

Slide 48 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 48/78 原始的方法の欠点 原始的方法の欠点 計測箇所ごとにコード修正が必要 細かく見ようとするとダルい 分かりやすくI/Oがボトルネックになっているケース以外で困る

Slide 49

Slide 49 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 49/78 Pro ler とは Pro ler とは 性能解析のためのツール プログラムの各処理の実行性能を収集 統計情報を出力

Slide 50

Slide 50 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 50/78 2種類の計測方式 2種類の計測方式

Slide 51

Slide 51 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 51/78 関数呼び出しフック方式(以降フック方式) サンプリング方式

Slide 52

Slide 52 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 52/78 フック方式 フック方式

Slide 53

Slide 53 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 53/78 フック方式の仕組み フック方式の仕組み op_array を実行する zend_execute_ex() は実際には関数ポインタ 拡張から以下のような処理へ差し替える 開始時刻を取得 差し替え前の zend_execute_ex() を呼び出す 終了時刻を取得 「呼び出し元関数名 + 関数名」をキーにした連想配列へ全呼 び出しを記録

Slide 54

Slide 54 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 54/78 フック方式の例 フック方式の例 xdebug xhprof tideways black re spx

Slide 55

Slide 55 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 55/78 xhprof xhprof Facebook 製の拡張 HHVM 移行で PHP7 未対応のまま捨てられた 関数フック方式 全関数呼び出しをフック black re や tideways 等に派生 xdebug のより軽いのがウリ

Slide 56

Slide 56 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 56/78

Slide 57

Slide 57 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 57/78

Slide 58

Slide 58 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 58/78 tideways tideways 元は xhprof からの派生プロファイラ 区間計測機能を持つ 現在は xhprof ベースではなく、0 からリライトされたクローズドソー スの拡張に xhprof 互換機能のみ切り出した tideways_xhprof が OSS に xhprof 互換の無料で使えるプロファイラの中でもよくメンテされて る

Slide 59

Slide 59 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 59/78

Slide 60

Slide 60 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 60/78

Slide 61

Slide 61 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 61/78 フック型の計測オーバーヘッド フック型の計測オーバーヘッド 計測関数の呼び出しによるオーバヘッド VM のネスト呼び出しによるオーバヘッド これらがほぼ固定時間でかかる チリツモで効く

Slide 62

Slide 62 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 62/78

Slide 63

Slide 63 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 63/78 PHP8 の JIT PHP8 の JIT 来年末に PHP8 リリース JIT が入る I/O バウンドな処理にはあまり関係ないが、PHP コード自体は速く なる VM の実行方式が変わって zend_execute_ex() フックが効かなくな るかも AST 操作等で自動で計測処理を仕込む手もあるが、オーバーヘッ ド問題大きくなる

Slide 64

Slide 64 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 64/78 フック方式まとめ フック方式まとめ あらゆる関数呼び出しを計測可能 計測する箇所ごとに計測コードを差し込む必要がない 計測対象の大量呼び出しに弱い 分かりやすいボトルネックがある場合に便利

Slide 65

Slide 65 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 65/78 サンプリング方式 サンプリング方式

Slide 66

Slide 66 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 66/78 サンプリング方式の仕組み サンプリング方式の仕組み 計測対象と平行動作するプログラムが VM の状態をサンプリング して監視 ExecutorGlobals や VM の呼び出しスタック、実行中の命令位置 など サンプリングしたタイミングでよく実行されている箇所が重い箇所

Slide 67

Slide 67 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 67/78

Slide 68

Slide 68 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 68/78 サンプリング方式の例 サンプリング方式の例 nikic/sample_prof adsr/phpspy krakjoe/trace

Slide 69

Slide 69 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 69/78 nikic/sample_prof nikic/sample_prof C 言語拡張 ソースコードの行レベルで計測がとれる 各関数に散財する変数代入がチリツモで遅い、とかにも気付ける 極度にCPU バウンドな処理で型検査のコストが重い可能性とかに も気付ける

Slide 70

Slide 70 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 70/78 内部はプロファイラの開始時に pthread_create() でスレッドを起動 指定したサンプリングレートで計測スレッドが VM の状態を監視

Slide 71

Slide 71 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 71/78 // 起動自体は他プロファイラと似たような方法でいける sample_prof_start(50); // ここでスレッド生成 register_shutdown_function(function(){ sample_prof_end(); $data = sample_prof_get_data(); file_put_contents(uniqid() . 'prof.txt', serialize($data)); });

Slide 72

Slide 72 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 72/78 adsr/phpspy adsr/phpspy スタンドアロンの C 言語プログラム 拡張ではない

Slide 73

Slide 73 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 73/78

Slide 74

Slide 74 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 74/78 PHP 処理系のメモリレイアウトについての情報を自前で持つ 別プロセスからメモリを読んで VM の内部状態を監視 性能計測もできれば変数も覗き見れる 暴走無限ループ時にコードのどこを実行中かも見れる

Slide 75

Slide 75 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 75/78

Slide 76

Slide 76 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 76/78 Ruby の rbspy が元ネタ

Slide 77

Slide 77 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 77/78 サンプリング方式まとめ サンプリング方式まとめ 計測対象の処理に影響を与えず計測できる 関数呼び出し関係の情報はある程度犠牲になる 大量に呼び出される関数があってもオーバヘッドの累積が無い

Slide 78

Slide 78 text

2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 78/78 おしまい おしまい