徹底検証!PHP最適化Tips

第4回 制御構造,for文にまつわる噂を検証

この記事を読むのに必要な時間:およそ 4 分

次に,実装を見てどこでopcodeが設定されているかPHPのコードを読んでいきましょう。

191     |   T_FOR
192             '('
193                 for_expr
194             ';' { zend_do_free(&$3 TSRMLS_CC); $4.u.opline_num = get_next_op_number(CG(active_op_array)); }
195                 for_expr
196             ';' { zend_do_extended_info(TSRMLS_C); zend_do_for_cond(&$6, &$7 TSRMLS_CC); }
197                 for_expr
198             ')' { zend_do_free(&$9 TSRMLS_CC);  zend_do_for_before_statement(&$4, &$7 TSRMLS_CC); }
199             for_statement { zend_do_for_end(&$7 TSRMLS_CC); }

上記はphp-5.2.6/Zend/zend_language_parser.yに書かれてるfor文の構文解析部分のコードになります。

“for(A;B;C){D}⁠を用いて説明すると,193行目ではAが処理され,195行目では条件式Bが処理され,197行目ではCが処理され,199行目ではfor文内で実行されるステートメントDの処理されます。for文は191行目から順に解析されopcodeが登録されていきます。

続いて,for文の解析時に呼ばれる代表的な関数について説明を行います。

1548 void zend_do_end_function_call(znode *function_name, znode *result, znode *argument_list, int is_method, in     t is_dynamic_fcall TSRMLS_DC)
1549 {
1550     zend_op *opline;
1551 
1552     if (is_method && function_name && function_name->op_type == IS_UNUSED) {
1553         /* clone */
1554         if (Z_LVAL(argument_list->u.constant) != 0) {
1555             zend_error(E_WARNING, "Clone method does not require arguments");
1556         }
1557         opline = &CG(active_op_array)->opcodes[Z_LVAL(function_name->u.constant)];
1558     } else {
1559         opline = get_next_op(CG(active_op_array) TSRMLS_CC);
1560         if (!is_method && !is_dynamic_fcall && function_name->op_type==IS_CONST) {
1561             opline->opcode = ZEND_DO_FCALL;
1562             opline->op1 = *function_name;
1563         } else {
1564             opline->opcode = ZEND_DO_FCALL_BY_NAME;
1565             SET_UNUSED(opline->op1);
1566         }
1567     }

zend_do_end_function_call()はcount()部分の解析時に呼ばれます。1561行目でZEND_DO_FCALLがopcodeに設定され,1562行目でop1にcount関数の実行がznodeとして設定されます。

736 void zend_do_for_cond(znode *expr, znode *second_semicolon_token TSRMLS_DC)
737 {
738     int for_cond_op_number = get_next_op_number(CG(active_op_array));
739     zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
740 
741     opline->opcode = ZEND_JMPZNZ;
742     opline->op1 = *expr;  /* the conditional expression */
743     second_semicolon_token->u.opline_num = for_cond_op_number;
744     SET_UNUSED(opline->op2);
745 }

zend_do_for_cond()では741行目でopcodeにZEND_JMPZNZが設定されます。

748 void zend_do_for_before_statement(znode *cond_start, znode *second_semicolon_token TSRMLS_DC)
749 {
750     zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
751 
752     opline->opcode = ZEND_JMP;
753     opline->op1.u.opline_num = cond_start->u.opline_num;
754     CG(active_op_array)->opcodes[second_semicolon_token->u.opline_num].extended_value = get_next_op_number(     CG(active_op_array));
755     SET_UNUSED(opline->op1);
756     SET_UNUSED(opline->op2);
757 
758     do_begin_loop(TSRMLS_C);
759 
760     INC_BPC(CG(active_op_array));
761 }

zend_do_for_before_statement()ではopcodeにZEND_JMPが設定されます。ジャンプ先にはop1.u.opline_numにfor文の条件式部分が設定されます。

764 void zend_do_for_end(znode *second_semicolon_token TSRMLS_DC)
765 {
766     zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
767 
768     opline->opcode = ZEND_JMP;
769     opline->op1.u.opline_num = second_semicolon_token->u.opline_num+1;
770     CG(active_op_array)->opcodes[second_semicolon_token->u.opline_num].op2.u.opline_num = get_next_op_numbe     r(CG(active_op_array));
771     SET_UNUSED(opline->op1);
772     SET_UNUSED(opline->op2);
773 
774     do_end_loop(second_semicolon_token->u.opline_num+1, 0 TSRMLS_CC);
775 
776     DEC_BPC(CG(active_op_array));
777 }

zend_do_for_end()ではopcodeにZEND_JMPが,ジャンプ先にはfor(A;B;C)のCの部分が設定されます。

このように,先ほどvldで出力したopcodeとfor文の実行との関係が簡単ではありますがわかったと思います。

話がだいぶずれてしまいましたが,まとめるとfor文内の条件式に関数を設定すると,関数の実行が条件式が評価されるたびに起きるために遅くなってました。tips通りfor文内の条件式では関数を使わずにあらかじめ変数に格納して使ったほうがよいという結論になりました。

おわりに

これまで4回にわたってお送りした「徹底検証! PHP最適化Tips」の連載は今回で最後になります。PHP最適化TipsをもとにPHPのソースを読み,実装を知ることでその有効性について説明を行ってきました。

本連載をきっかけにさらにPHPに興味を持っていただけたら幸いです。

著者プロフィール

梶原大輔(かじわらだいすけ)

大学卒業後,ヤフー株式会社に入社し,Yahoo!ビデオキャストなどのサービス開発に従事。

現在はグリー株式会社でバックエンドシステムの開発を担当。

URLhttp://d.hatena.ne.jp/kajidai/