phpperformanceforeachlambdaarray-map

Performance of foreach, array_map with lambda and array_map with static function


What's the performance difference (if there is any) between these three approaches, both used to transform an array to another array?

  1. Using foreach
  2. Using array_map with lambda/closure function
  3. Using array_map with 'static' function/method
  4. Is there any other approach?

To make myself clear, let's have look at the examples, all doing the same - multiplying the array of numbers by 10:

$numbers = range(0, 1000);

Foreach

$result = array();
foreach ($numbers as $number) {
    $result[] = $number * 10;
}
return $result;

Map with lambda

return array_map(function($number) {
    return $number * 10;
}, $numbers);

Map with 'static' function, passed as string reference

function tenTimes($number) {
    return $number * 10;
}
return array_map('tenTimes', $numbers);

Is there any other approach? I will be happy to hear actually all differences between the cases from above, and any inputs why one should be used instead of others.


Solution

  • FWIW, I just did the benchmark since poster didn't do it. Running on PHP 5.3.10 + XDebug.

    UPDATE 2015-01-22 compare with mcfedr's answer below for additional results without XDebug and a more recent PHP version.

    
    function lap($func) {
      $t0 = microtime(1);
      $numbers = range(0, 1000000);
      $ret = $func($numbers);
      $t1 = microtime(1);
      return array($t1 - $t0, $ret);
    }
    
    function useForeach($numbers)  {
      $result = array();
      foreach ($numbers as $number) {
          $result[] = $number * 10;
      }
      return $result;
    }
    
    function useMapClosure($numbers) {
      return array_map(function($number) {
          return $number * 10;
      }, $numbers);
    }
    
    function _tenTimes($number) {
        return $number * 10;
    }
    
    function useMapNamed($numbers) {
      return array_map('_tenTimes', $numbers);
    }
    
    foreach (array('Foreach', 'MapClosure', 'MapNamed') as $callback) {
      list($delay,) = lap("use$callback");
      echo "$callback: $delay\n";
    }
    
    

    I get pretty consistent results with 1M numbers across a dozen attempts:

    Supposing the lackluster speed of the map on closure was caused by the closure possibly being evaluated each time, I also tested like this:

    
    function useMapClosure($numbers) {
      $closure = function($number) {
        return $number * 10;
      };
    
      return array_map($closure, $numbers);
    }
    

    But the results are identical, confirming that the closure is only evaluated once.

    2014-02-02 UPDATE: opcodes dump

    Here are the opcode dumps for the three callbacks. First useForeach():

    
    
    compiled vars:  !0 = $numbers, !1 = $result, !2 = $number
    line     # *  op                           fetch          ext  return  operands
    ---------------------------------------------------------------------------------
      10     0  >   EXT_NOP                                                  
             1      RECV                                                     1
      11     2      EXT_STMT                                                 
             3      INIT_ARRAY                                       ~0      
             4      ASSIGN                                                   !1, ~0
      12     5      EXT_STMT                                                 
             6    > FE_RESET                                         $2      !0, ->15
             7  > > FE_FETCH                                         $3      $2, ->15
             8  >   OP_DATA                                                  
             9      ASSIGN                                                   !2, $3
      13    10      EXT_STMT                                                 
            11      MUL                                              ~6      !2, 10
            12      ASSIGN_DIM                                               !1
            13      OP_DATA                                                  ~6, $7
      14    14    > JMP                                                      ->7
            15  >   SWITCH_FREE                                              $2
      15    16      EXT_STMT                                                 
            17    > RETURN                                                   !1
      16    18*     EXT_STMT                                                 
            19*   > RETURN                                                   null
    

    Then the useMapClosure()

    
    compiled vars:  !0 = $numbers
    line     # *  op                           fetch          ext  return  operands
    ---------------------------------------------------------------------------------
      18     0  >   EXT_NOP                                                  
             1      RECV                                                     1
      19     2      EXT_STMT                                                 
             3      EXT_FCALL_BEGIN                                          
             4      DECLARE_LAMBDA_FUNCTION                                  '%00%7Bclosure%7D%2Ftmp%2Flap.php0x7f7fc1424173'
      21     5      SEND_VAL                                                 ~0
             6      SEND_VAR                                                 !0
             7      DO_FCALL                                      2  $1      'array_map'
             8      EXT_FCALL_END                                            
             9    > RETURN                                                   $1
      22    10*     EXT_STMT                                                 
            11*   > RETURN                                                   null
    

    and the closure it calls:

    
    compiled vars:  !0 = $number
    line     # *  op                           fetch          ext  return  operands
    ---------------------------------------------------------------------------------
      19     0  >   EXT_NOP                                                  
             1      RECV                                                     1
      20     2      EXT_STMT                                                 
             3      MUL                                              ~0      !0, 10
             4    > RETURN                                                   ~0
      21     5*     EXT_STMT                                                 
             6*   > RETURN                                                   null
    

    then the useMapNamed() function:

    
    compiled vars:  !0 = $numbers
    line     # *  op                           fetch          ext  return  operands
    ---------------------------------------------------------------------------------
      28     0  >   EXT_NOP                                                  
             1      RECV                                                     1
      29     2      EXT_STMT                                                 
             3      EXT_FCALL_BEGIN                                          
             4      SEND_VAL                                                 '_tenTimes'
             5      SEND_VAR                                                 !0
             6      DO_FCALL                                      2  $0      'array_map'
             7      EXT_FCALL_END                                            
             8    > RETURN                                                   $0
      30     9*     EXT_STMT                                                 
            10*   > RETURN                                                   null
    

    and the named function it calls, _tenTimes():

    
    compiled vars:  !0 = $number
    line     # *  op                           fetch          ext  return  operands
    ---------------------------------------------------------------------------------
      24     0  >   EXT_NOP                                                  
             1      RECV                                                     1
      25     2      EXT_STMT                                                 
             3      MUL                                              ~0      !0, 10
             4    > RETURN                                                   ~0
      26     5*     EXT_STMT                                                 
             6*   > RETURN                                                   null