Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPの関数実行とその計測

sji
January 29, 2019

 PHPの関数実行とその計測

PHPカンファレンス福岡2019
https://www.youtube.com/watch?v=-WJG7R5bEvg

sji

January 29, 2019
Tweet

More Decks by sji

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  8. 2019/6/29 reveal.js
    localhost:8000/?print-pdf/#/ 8/78
    function sum(int $a, int $b): int {
    return $a + $b;
    }
    echo sum(1, 2);

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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
    ;

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  33. 2019/6/29 reveal.js
    localhost:8000/?print-pdf/#/ 33/78
    // function 擬似的なmain関数() {
    function sum(int $a, int $b): int {
    return $a + $b;
    }
    echo sum(1, 2);
    // }

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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
    拡張等から色々差し替え可能

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  46. 2019/6/29 reveal.js
    localhost:8000/?print-pdf/#/ 46/78
    原始的計測
    原始的計測
    function sum(int $a, int $b): int {
    return $a + $b;
    }
    $start = microtime(true);
    $result = sum(1, 2);
    $end = microtime(true) - $start;
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  63. 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 操作等で自動で計測処理を仕込む手もあるが、オーバーヘッ
    ド問題大きくなる

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide