Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

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.

share|improve this question
4  
Why don't you just benchmark and see what happens? –  Jon Aug 9 '13 at 10:38
3  
Well, I may make a benchmark. But I still do not know how it internally works. Even if I find out one is faster, I still do not know why. Is it because of the PHP version? Does it depend on the data? Is there a difference between associative and ordinary arrays? Of course I can make whole suite of benchmarks but getting some theory saves here a lot of time. I hope you understand... –  Pavel S. Aug 9 '13 at 14:21

3 Answers 3

up vote 22 down vote accepted

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:

  • Foreach: 0.7 sec
  • Map on closure: 3.4 sec
  • Map on function name: 1.2 sec.

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

share|improve this answer
    
Thanks for the benchmarks. However, I would like to know why there is such difference. Is it because of a function call overhead? –  Pavel S. Jan 25 '14 at 13:06
2  
I added the opcode dumps in the issue. First thing we can see is that the named function and closure have exactly the same dump, and they are called via array_map in much the same way, with just one exception: the closure call includes one more opcode DECLARE_LAMBDA_FUNCTION, which explains why using it is a bit slower than using the named function. Now, comparing the array loop vs array_map calls, everything in the array loop is interpreted inline, without any call to a function, meaning no context to push/pop, just a JMP at the end of the loop, which likely explains the big difference. –  FGM Feb 2 '14 at 17:35
1  
I have just tried this using a built-in function (strtolower), and in that case, useMapNamed is actually faster than useArray. Thought that was worth mentioning. –  DisgruntledGoat Aug 30 '14 at 16:57
    
In lap, don't you want the range() call above the first microtime call? (Though probably insignificant compared with the time for the loop.) –  contrebis Jan 27 at 15:53
    
@FGM not sure why you called it "useArray" as all methods work on an array. I've modified to "useForeach" as this is what it really does. –  Michael Härtl Apr 2 at 12:56

Its interesting to run this benchmark with xdebug disabled, as xdebug adds quite a lot of overhead, esp to function calls.

This is FGM's script run using 5.6 With xdebug

Array: 0.79232501983643
MapClosure: 4.1082420349121
MapNamed: 1.7884571552277

Without xdebug

Array: 0.69830799102783
MapClosure: 0.78584599494934
MapNamed: 0.85125398635864

Here there is only a very small difference between the foreach and closure version.

Its also interesting to add a version with a closure with a use

function useMapClosureI($numbers) {
  $i = 10;
  return array_map(function($number) use ($i) {
      return $number * $i++;
  }, $numbers);
}

For comparison I add:

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

Here we can see it makes an impact on the closure version, whereas the array hasn't noticeably changed.

Array: 0.68645691871643
MapClosure: 0.8039448261261
ArrayI: 0.68821406364441
MapClosureI: 0.8894100189209
share|improve this answer

It's interesting. But I've got an opposite result with the following codes which are simplified from my current projects:


    // test a simple array_map in the real world.
    function test_array_map($data){
        return array_map(function($row){
            return array(
                'productId' => $row['id'] + 1,
                'productName' => $row['name'],
                'desc' => $row['remark']
            );
        }, $data);
    }

    // Another with local variable $i
    function test_array_map_use_local($data){
        $i = 0;
        return array_map(function($row) use ($i) {
            $i++;
            return array(
                'productId' => $row['id'] + $i,
                'productName' => $row['name'],
                'desc' => $row['remark']
            );
        }, $data);
    }

    // test a simple foreach in the real world
    function test_foreach($data){
        $result = array();
        foreach ($data as $row) {
            $tmp = array();
            $tmp['productId'] = $row['id'] + 1;
            $tmp['productName'] = $row['name'];
            $tmp['desc'] = $row['remark'];
            $result[] = $tmp;
        }
        return $result;
    }

    // Another with local variable $i
    function test_foreach_use_local($data){
        $result = array();
        $i = 0;
        foreach ($data as $row) {
            $i++;
            $tmp = array();
            $tmp['productId'] = $row['id'] + $i;
            $tmp['productName'] = $row['name'];
            $tmp['desc'] = $row['remark'];
            $result[] = $tmp;
        }
        return $result;
    }

Here is my testing data and codes:


    $data = array_fill(0, 10000, array(
        'id' => 1,
        'name' => 'test',
        'remark' => 'ok'
    ));

    $tests = array(
        'array_map' => array(),
        'foreach' => array(),
        'array_map_use_local' => array(),
        'foreach_use_local' => array(),
    );

    for ($i = 0; $i  &$records) {
            $start = microtime(true);
            call_user_func("test_$testName", $data);
            $delta = microtime(true) - $start;
            $records[] = $delta;
        }
    }

    // output result:
    foreach ($tests as $name => &$records) {
        printf('%.4f : %s '.PHP_EOL, 
                  array_sum($records) / count($records), $name);
    }

The result is:

0.0098 : array_map
0.0114 : foreach
0.0114 : array_map_use_local
0.0115 : foreach_use_local

My tests were in LAMP production environment without xdebug. I'am wandering xdebug would slow down array_map's performance.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.