PHPのVLDでfor文を見るの記事の続き

PHPのfor文の書き方で、

//$arrには配列型のデータが格納されている
for($i = 0; $i > count($arr); ++$i){
        //処理を書く
}

上記のコードのようなforの二番目の式でcount(配列)を書くのはよろしくないと記載されていることが多い。

理由はfor文による繰り返しの際に都度、count関数を実行する事になり、関数の実行は処理が重いからすべきではないという。


実際にどれ程遅くなるのか?見てみよう。




環境

Ubuntu 20.04

PHP 7.4.6


先にオペコードを確認してみる。


<?php
$arr = range(1,1000000);
for($i = 0; $i < count($arr); ++$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 = 8
Branch analysis from position: 8
2 jumps found. (Code = 44) Position 1 = 11, Position 2 = 7
Branch analysis from position: 11
1 jumps found. (Code = 62) Position 1 = -2
Branch analysis from position: 7
2 jumps found. (Code = 44) Position 1 = 11, Position 2 = 7
Branch analysis from position: 11
Branch analysis from position: 7
filename:       /path/to/dir/for.php
function name:  (null)
number of ops:  12
compiled vars:  !0 = $arr, !1 = $i
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    2     0  E >   INIT_FCALL                                               'range'
          1        SEND_VAL                                                 1
          2        SEND_VAL                                                 1000000
          3        DO_ICALL                                         $2      
          4        ASSIGN                                                   !0, $2
    3     5        ASSIGN                                                   !1, 0
          6      > JMP                                                      ->8
          7    >   PRE_INC                                                  !1
          8    >   COUNT                                            ~6      !0
          9        IS_SMALLER                                       ~7      !1, ~6
         10      > JMPNZ                                                    ~7, ->7
    6    11    > > RETURN                                                   1

branch: #  0; line:     2-    3; sop:     0; eop:     6; out0:   8
branch: #  7; line:     3-    3; sop:     7; eop:     7; out0:   8
branch: #  8; line:     3-    3; sop:     8; eop:    10; out0:  11; out1:   7; out2:  11; out3:   7
branch: # 11; line:     6-    6; sop:    11; eop:    11; out0:  -2
path #1: 0, 8, 11, 
path #2: 0, 8, 7, 8, 11, 
path #3: 0, 8, 7, 8, 11, 
path #4: 0, 8, 11, 
path #5: 0, 8, 7, 8, 11, 
path #6: 0, 8, 7, 8, 11,  

詳細は端折るけれども、7〜10の処理を何度も繰り返して、8のCOUNTも何度も実行していることになる。

これが遅くなる原因か。


ということで、処理時間を計測してみることにする。




<?php
$start = microtime(true);
$arr = range(1,1000000);
for($i = 0; $i < count($arr); ++$i){
        //処理を書く
}
$end = microtime(true);
echo $end - $start;

計測用で上記のコードを用意した。

実行と結果は下記の通り。

php /path/to/dir/for.php
0.017987966537476

続いて、forの2番目の式のcount関数の処理を事前に実行しておくコードで実行速度を測定してみる。

<?php
$start = microtime(true);
$arr = range(1,1000000);
$cnt = count($arr);
for($i = 0; $i < $cnt; ++$i){
        //処理を書く
}
$end = microtime(true);
echo $end - $start;

実行と結果は下記の通り。

php /path/to/dir/for.php
0.018435955047607

あれ?実行速度がほとんど変わらない。

後者のオペコードは記載していないが、COUNTの実行回数が全然違うのは自明であるので、実はCOUNTの処理自体がそんなに遅くないとか?

もしくは同じコードであると判断され、実行は一回のみで結果を記録しておいて二回目以降は結果を参照しているとか?