$arr = range(1, 100000);

のような配列があった場合の繰り返しで、

$cnt = count($arr);
for($i = 0; $i < $cnt; ++$i){
        //処理を書く
}

foreach($arr as $i => $v){
        //
}

上記のようなfor文かforeach文のどちらを使えば良いか迷う。

そんな時はVLDでオペコードを確認してみたり、実行速度を確認してみると迷いがなくなる。




先にPHPのVLDでfor文を見る2の記事で見たfor文の方のオペコードを確認する。

<?php
$arr = range(1, 100000);
$cnt = count($arr);
for($i = 0; $i < $cnt; ++$i){
        $v = $arr[$i];
        //以後の処理は省略
}

※foreachの方で配列のインデックスと値を参照できるようにforの方でも合わせた。


上記のコードの結果は下記の通り。

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

branch: #  0; line:     2-    4; sop:     0; eop:     8; out0:  12
branch: #  9; line:     5-    4; sop:     9; eop:    11; out0:  12
branch: # 12; line:     4-    4; sop:    12; eop:    13; out0:  14; out1:   9; out2:  14; out3:   9
branch: # 14; line:     8-    8; sop:    14; eop:    14; out0:  -2
path #1: 0, 12, 14, 
path #2: 0, 12, 9, 12, 14, 
path #3: 0, 12, 9, 12, 14, 
path #4: 0, 12, 14, 
path #5: 0, 12, 9, 12, 14, 
path #6: 0, 12, 9, 12, 14, 

結果に関しては特に触れずに続いてforeach文の方を見てみる。

<?php
$arr = range(1, 100000);
foreach($arr as $i => $v){
        //以後の処理は省略
}

上記のコードの結果は下記の通り。

$ php -d vld.active=1 -d vld.execute=0 /path/to/dir/foreach.php
Finding entry points
Branch analysis from position: 0
2 jumps found. (Code = 77) Position 1 = 6, Position 2 = 9
Branch analysis from position: 6
2 jumps found. (Code = 78) Position 1 = 7, Position 2 = 9
Branch analysis from position: 7
1 jumps found. (Code = 42) Position 1 = 6
Branch analysis from position: 6
Branch analysis from position: 9
1 jumps found. (Code = 62) Position 1 = -2
Branch analysis from position: 9
filename:       /path/to/dir/foreach.php
function name:  (null)
number of ops:  11
compiled vars:  !0 = $arr, !1 = $v, !2 = $i
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    2     0  E >   INIT_FCALL                                               'range'
          1        SEND_VAL                                                 1
          2        SEND_VAL                                                 100000
          3        DO_ICALL                                         $3      
          4        ASSIGN                                                   !0, $3
    3     5      > FE_RESET_R                                       $5      !0, ->9
          6    > > FE_FETCH_R                                       ~6      $5, !1, ->9
          7    >   ASSIGN                                                   !2, ~6
          8      > JMP                                                      ->6
          9    >   FE_FREE                                                  $5
    6    10      > RETURN                                                   1

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

for文とforeach文ともに、配列のインデックス($i)と値($v)を参照できる状態にして、各々を確認するとforeachの方がコードが4行短くなっていた。




処理速度を確認してみると、for文の方が

php /path/to/dir/for.php
0.0024669170379639

で、foreach文の方が、

$ php /path/to/dir/foreach.php
0.0026559829711914

と大差なかった。

※処理速度の確認方法はPHPのVLDでfor文を見るに記載されている方法を採用している。


オペコードの行数が減るという視点から今回のようなケースではforeachの方が良いのかなと。

オペコードの行数がOPCacheにどのような影響を与えるか?を知る必要がありそうだ。

OPCache - PHP