はじめに
今回はfor文に関するtipsについて解説していきます。
for文の条件式にはcount($array)のような関数をいれない(変数に格納)
for文の条件式に関数を入れると遅くなるというtipsです。
例えば配列の要素数だけfor文を用いてループを回したいときに、配列の要素数を取得できるcount()関数を使って下記のように書くことができます。
このtipsでは下記のようにあらかじめ変数に格納してからの使用を勧めてます。
まずはサンプルプログラムを用意し、ベンチマークをとってみます。
tips通りfor文の条件式に関数を用いないほうが速い結果となりました。それではどうしてこのような結果になるのか検証していきたいと思います。
vldを用いた解析
今回ははじめにvldを用いて解析を行います。vld(Vulcan Logic Disassembler)はPHP用のディスアセンブラで、opcodeレベルでのPHPコードの解析に役立ちます。
まずは、vldをインストールします。
上記の手順でインストールすることができます。
それではvldを使ってみます。サンプルコードとして簡単なfor文のコードを用意します。
実行方法はvld.active=1をphp.iniに設定するか、コマンドラインオプションに付け加えるとvldが有効になります。
実行結果は上記のようになりました。それでは、出力されたopcodeをもとに解析していきましょう。
2:DO_FCALLでcount()が実行されます。その結果をもとに3:IS_SMALLERで比較が行われ、
4:JMPZNZでは3:IS_SMALLERの結果がfalseであれば10:RETURNにジャンプし終了します。trueであればextended valueに設定されてる8:ECHOまで処理がジャンプし、forループ内のステートメントが実行され、9:JMPで5へジャンプしPOST_INCが実行され$iがインクリメントされます。そして7:JMPで1:SEND_VALへ処理がジャンプし再び2:DO_FCALLでcount()が実行されます。
このようにforループが続く間、条件式の評価が行われるたびにcount()が実行されることになります。
次に、実装を見てどこでopcodeが設定されているかPHPのコードを読んでいきましょう。
上記は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文の解析時に呼ばれる代表的な関数について説明を行います。
zend_do_end_function_call()はcount()部分の解析時に呼ばれます。1561行目でZEND_DO_FCALLがopcodeに設定され、1562行目でop1にcount関数の実行がznodeとして設定されます。
zend_do_for_cond()では741行目でopcodeにZEND_JMPZNZが設定されます。
zend_do_for_before_statement()ではopcodeにZEND_JMPが設定されます。ジャンプ先にはop1.u.opline_numにfor文の条件式部分が設定されます。
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に興味を持っていただけたら幸いです。