PHPのfor文の高速化の話題で、教科書等によく記載されている
for($i = 0; $i < 10 $i++){ //処理を書く }
ではなく、
for($i = 0; $i < 10 ++$i){ //処理を書く }
にした方が良いということを時々見かける。
実際のところはどうなのか?を処理速度の測定とVLDで確認してみる。
ちなみに$i++と++$iの違いは、if文等で、式を実行してから$iに1を加える(インクリメント)か、$iに1を加えてから式を実行するか?の違いがある。
$i = 2; if($i++ > 2){ //通過しない }
では、2行目のif文の式の箇所がfalse(偽)になるが、
$i = 2; if(++$i > 2){ //通過する }
であればtrue(真)になる。
環境
Ubuntu 20.04
PHP 7.4.6
まずは処理速度の方を確認する。
これからの話はすべてファイル名をfor.phpで話を進める。
前者の$i++の方のコードは下記のようにする。
<?php $start = microtime(true); for($i = 0; $i < 100000000; $i++){ //処理を書く } $end = microtime(true); echo $end - $start;
実行と結果は下記の通り
$ php /path/to/dir/for.php 0.51271295547485
続いて、++$iの方を確認してみる。
<?php $start = microtime(true); for($i = 0; $i < 100000000; ++$i){ //処理を書く } $end = microtime(true); echo $end - $start;
$ php /path/to/dir/for.php 0.29786896705627
後者のコードにするだけで、処理速度が3/5程になった。
オペコードはどうなるのか?を確認してみる。
<?php for($i = 0; $i < 10; $i++){ //処理を書く }
実行と結果は下記の通り
$ php -d vld.active=1 -d vld.execute=0 /path/to/dir/for.php Finding entry points Branch analysis from position: 0 1 jumps found. (Code = 42) Position 1 = 4 Branch analysis from position: 4 2 jumps found. (Code = 44) Position 1 = 6, Position 2 = 2 Branch analysis from position: 6 1 jumps found. (Code = 62) Position 1 = -2 Branch analysis from position: 2 2 jumps found. (Code = 44) Position 1 = 6, Position 2 = 2 Branch analysis from position: 6 Branch analysis from position: 2 filename: /path/to/dir/for.php function name: (null) number of ops: 7 compiled vars: !0 = $i line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > ASSIGN !0, 0 1 > JMP ->4 2 > POST_INC ~2 !0 3 FREE ~2 4 > IS_SMALLER ~3 !0, 10 5 > JMPNZ ~3, ->2 5 6 > > RETURN 1 branch: # 0; line: 2- 2; sop: 0; eop: 1; out0: 4 branch: # 2; line: 2- 2; sop: 2; eop: 3; out0: 4 branch: # 4; line: 2- 2; sop: 4; eop: 5; out0: 6; out1: 2; out2: 6; out3: 2 branch: # 6; line: 5- 5; sop: 6; eop: 6; out0: -2 path #1: 0, 4, 6, path #2: 0, 4, 2, 4, 6, path #3: 0, 4, 2, 4, 6, path #4: 0, 4, 6, path #5: 0, 4, 2, 4, 6, path #6: 0, 4, 2, 4, 6,
オペコードに関する細かい話は置いといて、続いて下記のコードの方を確認する。
<?php for($i = 0; $i < 10; ++$i){ //処理を書く }
実行と結果は下記の通り
$ php -d vld.active=1 -d vld.execute=0 /path/to/dir/for.php Finding entry points Branch analysis from position: 0 1 jumps found. (Code = 42) Position 1 = 3 Branch analysis from position: 3 2 jumps found. (Code = 44) Position 1 = 5, Position 2 = 2 Branch analysis from position: 5 1 jumps found. (Code = 62) Position 1 = -2 Branch analysis from position: 2 2 jumps found. (Code = 44) Position 1 = 5, Position 2 = 2 Branch analysis from position: 5 Branch analysis from position: 2 filename: /path/to/dir/for.php function name: (null) number of ops: 6 compiled vars: !0 = $i line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > ASSIGN !0, 0 1 > JMP ->3 2 > PRE_INC !0 3 > IS_SMALLER ~3 !0, 10 4 > JMPNZ ~3, ->2 5 5 > > RETURN 1 branch: # 0; line: 2- 2; sop: 0; eop: 1; out0: 3 branch: # 2; line: 2- 2; sop: 2; eop: 2; out0: 3 branch: # 3; line: 2- 2; sop: 3; eop: 4; out0: 5; out1: 2; out2: 5; out3: 2 branch: # 5; line: 5- 5; sop: 5; eop: 5; out0: -2 path #1: 0, 3, 5, path #2: 0, 3, 2, 3, 5, path #3: 0, 3, 2, 3, 5, path #4: 0, 3, 5, path #5: 0, 3, 2, 3, 5, path #6: 0, 3, 2, 3, 5,
各々の結果で太字の箇所が違いだけれども、$i++から++$iにすると、オペコードが一行減っている。
おそらく、メモリの使用量でも少し削減されているはずだ。
for内の三番目の処理では、$i++と++$iのどちらも意味は変わらないので、積極的に++$iの方を使っていった方が良さそうだ。
余談
$i++の方にあるFREEのコードは内部で確保した値の解放という意味があるらしい。IS_SMALLERは2つの値の比較の処理(ここでは0行目で定義した変数の値とfor内の二番目の値の10の比較)なので、処理前に値を確保するためのメモリを確保しているのだろうか?
PHPの処理において、メモリの確保の処理は遅くなる傾向があるらしいので、++$iの方が速いのは、メモリの確保と解放が無いことに因るものでありそうだ。