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.

PHPの関数実行とその計測

625 views

Published on

PHPカンファレンス福岡2019

Published in: Engineering
  • Be the first to comment

PHPの関数実行とその計測

  1. 1. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 1/78 PHPの関数実行とその計測PHPの関数実行とその計測 五十嵐 進士 / @sji_ch
  2. 2. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 2/78 私は誰ですか私は誰ですか サーバサイドプログラマー 株式会社インフィニットループ たぶん月刊 PHP ニュース php-master-changes
  3. 3. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 3/78 仙台民、学生時代は佐世保民 PHP カンファレンス仙台とかやりました PHP 歴は累計 6 年くらい 元は趣味で C 言語を少し
  4. 4. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 4/78
  5. 5. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 5/78 AgendaAgenda PHP の関数実行の仕組みについて PHP の関数実行の計測について
  6. 6. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 6/78 PHP の関数とはPHP の関数とは
  7. 7. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 7/78 プログラムに行わせる一連の処理のまとまり
  8. 8. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 8/78 <?php function sum(int $a, int $b): int { return $a + $b; } echo sum(1, 2);
  9. 9. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 9/78 PHP の関数実行の仕組みPHP の関数実行の仕組み
  10. 10. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 10/78 ZendEngine の概要ZendEngine の概要 ZendEngine って知ってる? ZendEngine が何のことか大体分かる?
  11. 11. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 11/78 PHP 公式処理系の中心部(エンジン) PHP 4 の頃に Zeev さんと Andi さんが作った 2 字ずつとって Zend
  12. 12. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 12/78 ZendEngine = バイトコードコンパイラ + バイトコードを実行する VM
  13. 13. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 13/78 PHP コードは ZendVM のオペコードにコンパイルされる ZendVM(Executor) がオペコードを実行する
  14. 14. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 14/78
  15. 15. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 15/78
  16. 16. 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); }
  17. 17. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 17/78 コンパイルの流れコンパイルの流れ
  18. 18. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 18/78
  19. 19. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 19/78 字句解析字句解析 ↓ <?php function sum(int $a, int $b): int { return $a + $b; } echo sum(1, 2); T_OPEN_TAG T_FUNCTION T_STRING (T_STRING T_VARIABLE, T_STRING T_VARIABLE): T_STRING { T_RETURN T_VARIABLE + T_VARIABLE; } T_ECHO T_STRING(T_LNUMBER, T_LNUMBER);
  20. 20. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 20/78 構文解析構文解析
  21. 21. 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
  22. 22. 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
  23. 23. 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 ;
  24. 24. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 24/78 構文解析(構文木)構文解析(構文木) ↑(先の字句解析結果)を構文規則に当てはめると
  25. 25. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 25/78 構文解析(具象構文木)構文解析(具象構文木)
  26. 26. 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
  27. 27. 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
  28. 28. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 28/78 コード生成コード生成
  29. 29. 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
  30. 30. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 30/78 AST をオペコードにコンパイルAST をオペコードにコンパイル
  31. 31. 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
  32. 32. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 32/78 関数 と op_array関数 と op_array
  33. 33. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 33/78 <?php // function 擬似的なmain関数() { function sum(int $a, int $b): int { return $a + $b; } echo sum(1, 2); // }
  34. 34. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 34/78
  35. 35. 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
  36. 36. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 36/78 VMのスタックVMのスタック
  37. 37. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 37/78 ファイルの読み込みファイルの読み込み
  38. 38. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 38/78 ここ見ると大抵わかるここ見ると大抵わかる
  39. 39. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 39/78 処理系の標準処理のフック処理系の標準処理のフック
  40. 40. 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 拡張等から色々差し替え可能
  41. 41. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 41/78 ZendEngineでの関数実行まとめZendEngineでの関数実行まとめ PHP コードは関数の集まり、一見ないように見えても 関数はオペコードの集まり PHP コードはオペコードにコンパイルされる VM はメモリ上にその時々の状態を持つ ZendEngine の挙動は拡張等でカスタマイズできる
  42. 42. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 42/78 関数実行の計測関数実行の計測
  43. 43. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 43/78 プログラムの高速化 とりあえず何をするか
  44. 44. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 44/78 推測するな、計測せよ
  45. 45. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 45/78 ボトルネックボトルネック 実行時間の中の大きい割合を占める部分 経験則として、わりと一部コードが原因で全体の足を引っ張りがち ボトルネックを集中的に改善すると効率が良い
  46. 46. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 46/78 原始的計測原始的計測 <?php function sum(int $a, int $b): int { return $a + $b; } $start = microtime(true); $result = sum(1, 2); $end = microtime(true) - $start; }
  47. 47. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 47/78 原始的方法の良いところ原始的方法の良いところ 追加のライブラリやツールのインストールがいらない 任意の区間を計測対象に出来る DB/キャッシュアクセスの共通コードに仕込む等で大枠はつかめる
  48. 48. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 48/78 原始的方法の欠点原始的方法の欠点 計測箇所ごとにコード修正が必要 細かく見ようとするとダルい 分かりやすくI/Oがボトルネックになっているケース以外で困る
  49. 49. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 49/78 Pro ler とはPro ler とは 性能解析のためのツール プログラムの各処理の実行性能を収集 統計情報を出力
  50. 50. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 50/78 2種類の計測方式2種類の計測方式
  51. 51. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 51/78 関数呼び出しフック方式(以降フック方式) サンプリング方式
  52. 52. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 52/78 フック方式フック方式
  53. 53. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 53/78 フック方式の仕組みフック方式の仕組み op_array を実行する zend_execute_ex() は実際には関数ポインタ 拡張から以下のような処理へ差し替える 開始時刻を取得 差し替え前の zend_execute_ex() を呼び出す 終了時刻を取得 「呼び出し元関数名 + 関数名」をキーにした連想配列へ全呼 び出しを記録
  54. 54. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 54/78 フック方式の例フック方式の例 xdebug xhprof tideways black re spx
  55. 55. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 55/78 xhprofxhprof Facebook 製の拡張 HHVM 移行で PHP7 未対応のまま捨てられた 関数フック方式 全関数呼び出しをフック black re や tideways 等に派生 xdebug のより軽いのがウリ
  56. 56. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 56/78
  57. 57. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 57/78
  58. 58. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 58/78 tidewaystideways 元は xhprof からの派生プロファイラ 区間計測機能を持つ 現在は xhprof ベースではなく、0 からリライトされたクローズドソー スの拡張に xhprof 互換機能のみ切り出した tideways_xhprof が OSS に xhprof 互換の無料で使えるプロファイラの中でもよくメンテされて る
  59. 59. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 59/78
  60. 60. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 60/78
  61. 61. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 61/78 フック型の計測オーバーヘッドフック型の計測オーバーヘッド 計測関数の呼び出しによるオーバヘッド VM のネスト呼び出しによるオーバヘッド これらがほぼ固定時間でかかる チリツモで効く
  62. 62. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 62/78
  63. 63. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 63/78 PHP8 の JITPHP8 の JIT 来年末に PHP8 リリース JIT が入る I/O バウンドな処理にはあまり関係ないが、PHP コード自体は速く なる VM の実行方式が変わって zend_execute_ex() フックが効かなくな るかも AST 操作等で自動で計測処理を仕込む手もあるが、オーバーヘッ ド問題大きくなる
  64. 64. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 64/78 フック方式まとめフック方式まとめ あらゆる関数呼び出しを計測可能 計測する箇所ごとに計測コードを差し込む必要がない 計測対象の大量呼び出しに弱い 分かりやすいボトルネックがある場合に便利
  65. 65. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 65/78 サンプリング方式サンプリング方式
  66. 66. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 66/78 サンプリング方式の仕組みサンプリング方式の仕組み 計測対象と平行動作するプログラムが VM の状態をサンプリング して監視 ExecutorGlobals や VM の呼び出しスタック、実行中の命令位置 など サンプリングしたタイミングでよく実行されている箇所が重い箇所
  67. 67. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 67/78
  68. 68. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 68/78 サンプリング方式の例サンプリング方式の例 nikic/sample_prof adsr/phpspy krakjoe/trace
  69. 69. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 69/78 nikic/sample_profnikic/sample_prof C 言語拡張 ソースコードの行レベルで計測がとれる 各関数に散財する変数代入がチリツモで遅い、とかにも気付ける 極度にCPU バウンドな処理で型検査のコストが重い可能性とかに も気付ける
  70. 70. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 70/78 内部はプロファイラの開始時に pthread_create() でスレッドを起動 指定したサンプリングレートで計測スレッドが VM の状態を監視
  71. 71. 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)); });
  72. 72. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 72/78 adsr/phpspyadsr/phpspy スタンドアロンの C 言語プログラム 拡張ではない
  73. 73. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 73/78
  74. 74. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 74/78 PHP 処理系のメモリレイアウトについての情報を自前で持つ 別プロセスからメモリを読んで VM の内部状態を監視 性能計測もできれば変数も覗き見れる 暴走無限ループ時にコードのどこを実行中かも見れる
  75. 75. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 75/78
  76. 76. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 76/78 Ruby の rbspy が元ネタ
  77. 77. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 77/78 サンプリング方式まとめサンプリング方式まとめ 計測対象の処理に影響を与えず計測できる 関数呼び出し関係の情報はある程度犠牲になる 大量に呼び出される関数があってもオーバヘッドの累積が無い
  78. 78. 2019/6/29 reveal.js localhost:8000/?print-pdf/#/ 78/78 おしまいおしまい

×