복붙노트

정적 함수를 사용하여 foreach, array_map 및 lambda 및 array_map의 성능

PHP

정적 함수를 사용하여 foreach, array_map 및 lambda 및 array_map의 성능

이 세 가지 접근법 사이에 성능상의 차이점이 있다면 무엇입니까? 둘 다 배열을 다른 배열로 변환하는 데 사용됩니까?

나 자신을 분명히하기 위해 예제를 살펴 보자. 숫자 배열에 10을 곱하면된다.

$numbers = range(0, 1000);

각각

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

람다와지도

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

문자열 참조로 전달 된 '정적'함수로 매핑

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

다른 방법이 있습니까? 위에서 언급 한 사례와 실제로 다른 사람들 대신에 사용해야하는 입력에 대한 모든 차이점을 실제로 듣게되어 기쁘게 생각합니다.

해결법

  1. ==============================

    1.FWIW, 나는 포스터가 그것을하지 않았기 때문에 다만 기준을했다. PHP 5.3.10 + XDebug에서 실행.

    FWIW, 나는 포스터가 그것을하지 않았기 때문에 다만 기준을했다. PHP 5.3.10 + XDebug에서 실행.

    업데이트 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";
    }
    
    

    나는 수십 번의 시도를 통해 1M 수치로 꽤 일관된 결과를 얻는다.

    클로저에 대한지도의 미숙 한 속도가 매번 클로저가 평가 될 가능성이 있다고 가정 할 때, 나는 또한 다음과 같이 테스트했다.

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

    그러나 결과는 동일하며, 클로저는 한 번만 평가됩니다.

    2014-02-02 UPDATE : opcodes 덤프

    다음은 세 가지 콜백에 대한 opcode 덤프입니다. 먼저 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
    

    그런 다음 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
    

    그리고 그것을 호출하는 클로저 :

    
    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
    

    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  $0      'array_map'
             7      EXT_FCALL_END                                            
             8    > RETURN                                                   $0
      30     9*     EXT_STMT                                                 
            10*   > RETURN                                                   null
    

    명명 된 함수 인 _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
    
    
  2. ==============================

    2.xdebug가 비활성화 된 상태에서이 벤치 마크를 실행하는 것이 흥미 롭습니다. xdebug는 함수 호출에 많은 오버 헤드를 추가합니다.

    xdebug가 비활성화 된 상태에서이 벤치 마크를 실행하는 것이 흥미 롭습니다. xdebug는 함수 호출에 많은 오버 헤드를 추가합니다.

    이것은 5.6을 사용하여 실행되는 FGM의 스크립트입니다. xdebug 사용

    ForEach   : 0.79232501983643
    MapClosure: 4.1082420349121
    MapNamed  : 1.7884571552277
    

    xdebug가 없으면

    ForEach   : 0.69830799102783
    MapClosure: 0.78584599494934
    MapNamed  : 0.85125398635864
    

    foreach와 closure 버전 간에는 아주 작은 차이점이 있습니다.

    그것도 폐쇄와 함께 버전을 추가하는 재미있는

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

    비교를 위해 다음을 추가합니다.

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

    여기서 우리는 클로저 버전에 영향을 미치지 만 배열은 눈에 띄게 변경되지 않았다는 것을 알 수 있습니다.

    2014 년 11 월 19 일 비교를 위해 PHP 7 및 HHVM을 사용하여 결과를 추가했습니다. 결론은 비슷하지만 모든 것이 훨씬 빠릅니다.

    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
    
  3. ==============================

    3.흥미 롭군. 그러나 나는 현재 프로젝트에서 단순화 된 다음 코드와 반대의 결과를 얻었습니다.

    흥미 롭군. 그러나 나는 현재 프로젝트에서 단순화 된 다음 코드와 반대의 결과를 얻었습니다.

    // 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;
    }
    

    내 테스트 데이터 및 코드는 다음과 같습니다.

    $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);
    }
    

    결과는 다음과 같습니다.

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

    내 테스트는 xdebug가없는 LAMP 프로덕션 환경에서 수행되었습니다. 방황하는 xdebug는 array_map의 성능을 저하시킵니다.

  4. from https://stackoverflow.com/questions/18144782/performance-of-foreach-array-map-with-lambda-and-array-map-with-static-function by cc-by-sa and MIT license