php foreach、带有 lambda 的 array_map 和带有静态函数的 array_map 的性能

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18144782/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 17:03:32  来源:igfitidea点击:

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

phpperformanceforeachlambdaarray-map

提问by Pavel S.

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_mapwith lambda/closure function
  3. Using array_mapwith 'static' function/method
  4. Is there any other approach?
  1. 使用 foreach
  2. 使用array_map和λ/关闭功能
  3. 使用array_map带有“静态”函数/方法
  4. 还有其他方法吗?

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

为了清楚起见,让我们看一下示例,所有示例都执行相同的操作 - 将数字数组乘以 10:

$numbers = range(0, 1000);


Foreach

Foreach

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


Map with lambda

使用 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 alldifferences between the cases from above, and any inputs why one should be used instead of others.

还有其他方法吗?我会很高兴听到上述案例之间的所有差异,以及为什么应该使用一个而不是其他的任何输入。

采纳答案by FGM

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

FWIW,我只是做了基准测试,因为海报没有做。在 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.

更新 2015-01-22 与下面 mcfedr 的答案进行比较,以获得没有 XDebug 和更新的 PHP 版本的额外结果。


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:

我在十多次尝试中得到了非常一致的结果,其中有 100 万个数字:

  • Foreach: 0.7 sec
  • Map on closure: 3.4 sec
  • Map on function name: 1.2 sec.
  • Foreach:0.7 秒
  • 关闭时的地图:3.4 秒
  • 函数名称映射:1.2 秒。

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

2014-02-02 更新:操作码转储

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

这是三个回调的操作码转储。第一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                                               !0, ->15
         7  > > FE_FETCH                                               , ->15
         8  >   OP_DATA                                                  
         9      ASSIGN                                                   !2, 
  13    10      EXT_STMT                                                 
        11      MUL                                              ~6      !2, 10
        12      ASSIGN_DIM                                               !1
        13      OP_DATA                                                  ~6, 
  14    14    > JMP                                                      ->7
        15  >   SWITCH_FREE                                              
  15    16      EXT_STMT                                                 
        17    > RETURN                                                   !1
  16    18*     EXT_STMT                                                 
        19*   > RETURN                                                   null

Then the useMapClosure()

那么 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        'array_map'
         8      EXT_FCALL_END                                            
         9    > RETURN                                                   
  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:

那么useMapNamed()函数:


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  

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

'array_map' 7 EXT_FCALL_END 8 > RETURN
ForEach   : 0.79232501983643
MapClosure: 4.1082420349121
MapNamed  : 1.7884571552277
30 9* EXT_STMT 10* > RETURN null

and the named function it calls, _tenTimes():

以及它调用的命名函数_tenTimes()

ForEach   : 0.69830799102783
MapClosure: 0.78584599494934
MapNamed  : 0.85125398635864

回答by mcfedr

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

在禁用 xdebug 的情况下运行这个基准测试很有趣,因为 xdebug 增加了很多开销,尤其是函数调用。

This is FGM's script run using 5.6 With xdebug

这是使用 5.6 和 xdebug 运行的 FGM 脚本

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

Without xdebug

没有 xdebug

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

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

这里 foreach 和闭包版本之间只有很小的区别。

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

添加一个带有闭包的版本也很有趣 use

PHP 5.6

ForEach    : 0.57499806880951
MapClosure : 0.59327731132507
MapNamed   : 0.69694859981537
MapClosureI: 0.73265469074249
ForEachI   : 0.60068697929382

PHP 7

ForEach    : 0.11297199726105
MapClosure : 0.16404168605804
MapNamed   : 0.11067249774933
MapClosureI: 0.19481580257416
ForEachI   : 0.10989861488342

HHVM

ForEach    : 0.090071058273315
MapClosure : 0.10432276725769
MapNamed   : 0.1091267824173
MapClosureI: 0.11197068691254
ForEachI   : 0.092114186286926

For comparison I add:

为了比较,我补充说:

// 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 we can see it makes an impact on the closure version, whereas the array hasn't noticeably changed.

在这里我们可以看到它对闭包版本产生影响,而数组没有明显变化。

19/11/2015 I have also now added results using PHP 7 and HHVM for comparison. The conclusions are similar, though everything is much faster.

19/11/2015 我现在还添加了使用 PHP 7 和 HHVM 进行比较的结果。结论是相似的,尽管一切都快得多。

$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 < 100; $i++){
    foreach ($tests as $testName => &$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);
}

回答by Clarence

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

这真有趣。但是我使用以下代码得到了相反的结果,这些代码是从我当前的项目中简化而来的:

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

Here is my testing data and codes:

这是我的测试数据和代码:

##代码##

The result is:

结果是:

##代码##

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

我的测试是在没有 xdebug 的 LAMP 生产环境中进行的。我在徘徊 xdebug 会降低 array_map 的性能。