फॉर्च्यूनर का प्रदर्शन, लैम्ब्डा के साथ array_map और स्टेटिक फ़ंक्शन के साथ array_map


144

इन तीन दृष्टिकोणों के बीच प्रदर्शन अंतर (यदि कोई हो) है, दोनों एक सरणी को दूसरे सरणी में बदलने के लिए उपयोग किया जाता है?

  1. का उपयोग करते हुए foreach
  2. array_mapलैम्ब्डा / क्लोजर फ़ंक्शन के साथ उपयोग करना
  3. array_map'स्टैटिक' फंक्शन / विधि के साथ उपयोग करना
  4. क्या कोई और दृष्टिकोण है?

अपने आप को स्पष्ट करने के लिए, आइए उदाहरणों को देखें, सभी ऐसा ही कर रहे हैं - संख्याओं की संख्या को 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);

क्या कोई और दृष्टिकोण है? मुझे ऊपर से मामलों के बीच वास्तव में सभी मतभेदों को सुनने में खुशी होगी , और किसी भी इनपुट का उपयोग दूसरों के बजाय क्यों किया जाना चाहिए।


10
आप सिर्फ बेंचमार्क क्यों नहीं देखते और क्या होता है?
जॉन

17
ठीक है, मैं एक बेंचमार्क बना सकता हूं। लेकिन मुझे अभी तक नहीं पता है कि यह आंतरिक रूप से कैसे काम करता है। यहां तक ​​कि अगर मुझे पता है कि एक तेज है, मुझे अभी भी पता नहीं है कि क्यों। क्या यह PHP संस्करण के कारण है? क्या यह डेटा पर निर्भर करता है? क्या साहचर्य और साधारण सरणियों के बीच अंतर है? बेशक मैं पूरे बेंचमार्क बना सकता हूं, लेकिन कुछ थ्योरी मिलने से यहां काफी समय बच जाता है। मुझे आशा है कि आप समझ गए होंगे ...
Pavel S.

2
देर से टिप्पणी, लेकिन नहीं है (सूची ($ k, $ v) = प्रत्येक ($ सरणी)) उपरोक्त सभी से तेज? मैंने इसे php5.6 में बेंचमार्क नहीं किया है, लेकिन यह पहले के संस्करणों में था।
ओवेन बेर्स्फोर्ड

जवाबों:


121

एफडब्ल्यूआईडब्ल्यू, मैंने सिर्फ बेंचमार्क किया क्योंकि पोस्टर ने ऐसा नहीं किया। PHP 5.3.10 + XDebug पर चल रहा है।

अद्यतन 2015-01-22 XDebug के बिना अतिरिक्त परिणामों के लिए नीचे mcfedr के उत्तर और हाल ही के 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 संख्या के साथ बहुत लगातार परिणाम प्राप्त होते हैं:

  • Foreach: 0.7 सेकंड
  • क्लोजर पर नक्शा: 3.4 सेकंड
  • फ़ंक्शन नाम पर मानचित्र: 1.2 सेकंड।

बंद होने पर नक्शे की कमज़ोर गति को देखते हुए संभवतः हर बार मूल्यांकन बंद होने के कारण हुआ, मैंने भी इस तरह परीक्षण किया:


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

  return array_map($closure, $numbers);
}

लेकिन परिणाम समान हैं, यह पुष्टि करते हुए कि बंद का केवल एक बार मूल्यांकन किया जाता है।

2014-02-02 अद्यतन: opcodes डंप

यहां तीन कॉलबैक के लिए ओपोड डंप हैं। पहला 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

बेंचमार्क के लिए धन्यवाद। हालांकि, मैं जानना चाहूंगा कि ऐसा अंतर क्यों है। यह एक समारोह कॉल उपरि के कारण है?
पावेल एस।

4
मैं इस मुद्दे में opcode डंप जोड़ा। पहली बात हम देख सकते हैं कि नामित फ़ंक्शन और क्लोजर बिल्कुल एक ही डंप है, और उन्हें array_map के माध्यम से उसी तरह से बुलाया जाता है, सिर्फ एक अपवाद के साथ: क्लोजर कॉल में एक और opcode DECLARE_LAMBDA_FILCTION शामिल है, जो बताता है कि इसका उपयोग क्यों किया जा रहा है नामित फ़ंक्शन का उपयोग करने से थोड़ा धीमा। अब, सरणी लूप बनाम array_map कॉल्स की तुलना करते हुए, ऐरे लूप में सब कुछ इनलाइन की व्याख्या की जाती है, बिना किसी फ़ंक्शन को कॉल किए, जिसका अर्थ है कि पुश / पॉप के लिए कोई संदर्भ नहीं, लूप के अंत में सिर्फ एक JMP, जो संभवतः बड़े अंतर की व्याख्या करता है ।
FGM

4
मैंने सिर्फ एक अंतर्निहित फ़ंक्शन (स्ट्रेटोलॉवर) का उपयोग करके यह कोशिश की है, और उस मामले में, useMapNamedवास्तव में से तेज है useArray। सोचा कि ध्यान देने योग्य है।
असंतुष्टगीतगृह

1
में lap, क्या आप range()पहले माइक्रोटाइम कॉल के ऊपर कॉल नहीं चाहते हैं ? (हालांकि लूप के समय की तुलना में शायद
नाकाफी है

1
@billynoah PHP7.x वास्तव में बहुत तेज है। इस संस्करण द्वारा तैयार किए गए ऑपकोड को देखना दिलचस्प होगा, विशेष रूप से ओपचे के साथ तुलना के बिना / क्योंकि यह कोड कैशिंग के अलावा बहुत सारे अनुकूलन करता है।
FGM

232

Xdebug विकलांग के साथ इस बेंचमार्क को चलाने के लिए इसकी दिलचस्प बात है, क्योंकि xdebug बहुत अधिक ओवरहेड जोड़ता है, जो फ़ंक्शन कॉल करने के लिए esp है।

यह FGM की स्क्रिप्ट रन विथ 5.6 विद xdebug है

ForEach   : 0.79232501983643
MapClosure: 4.1082420349121
MapNamed  : 1.7884571552277

बिना xdebug के

ForEach   : 0.69830799102783
MapClosure: 0.78584599494934
MapNamed  : 0.85125398635864

यहां फॉर्च्यूनर और क्लोजर वर्जन के बीच बहुत कम अंतर है।

यह भी एक के साथ एक बंद के साथ एक संस्करण जोड़ने के लिए दिलचस्प है use

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

यहां हम यह देख सकते हैं कि यह क्लोजर वर्जन पर प्रभाव डालता है, जबकि एरे को बिल्कुल नहीं बदला गया है।

19/11/2015 मैंने अब तुलना के लिए 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

2
मैं आपको टाई तोड़कर और 51 वां उत्थान देकर आपको विजेता घोषित करता हूं। यह सुनिश्चित करने के लिए बहुत महत्वपूर्ण है कि परीक्षण परिणामों में परिवर्तन न करे! प्रश्न, हालांकि, "ऐरे" के लिए आपका परिणाम समय फॉरेस्ट लूप विधि है, है ना?
ब्यूटिक बटुक 3

2
उत्कृष्ट प्रतिक्रिया। यह देखना अच्छा है कि 7 कितना तेज है। मेरे व्यक्तिगत समय पर इसका उपयोग करना शुरू कर देंगे, अभी भी 5.6 काम पर हैं।
दान

1
तो हमें foreach के बजाय array_map का उपयोग क्यों करना चाहिए? अगर प्रदर्शन में यह खराब है तो इसे PHP में क्यों जोड़ा गया? क्या कोई विशिष्ट स्थिति है जिसके लिए foreach के बजाय array_map की आवश्यकता है? क्या कोई विशिष्ट तर्क है जिसे foreach नहीं संभाल सकता है और array_map हैंडल कर सकता है?
HendraWD

3
array_map(और इससे संबंधित कार्यों array_reduce, array_filter) आप सुंदर कोड लिखने करते हैं। अगर array_mapयह बहुत धीमा था foreach, तो array_mapयह उपयोग करने का एक कारण होगा , लेकिन यह बहुत समान है, इसलिए मैं हर जगह इसका उपयोग करूंगा यह समझ में आता है।
mcfedr

3
PHP7 देखने में बहुत सुधार हुआ है। मेरी परियोजनाओं के लिए एक अलग बैकएंड भाषा पर स्विच करने वाला था, लेकिन मैं PHP से चिपका रहूंगा।
realnsleo

8

यह दिलचस्प है। लेकिन मुझे निम्नलिखित कोड के साथ एक विपरीत परिणाम मिला है जो मेरी वर्तमान परियोजनाओं से सरल हैं:

// 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: फॉर्च्यूनर
0.0114: array_map_use_local
0.0115: foreach_use_local

मेरे परीक्षण xdebug के बिना LAMP उत्पादन वातावरण में थे। मैं घूम रहा हूँ xdebug array_map के प्रदर्शन को धीमा कर देगा।


यकीन नहीं होता अगर आपको @mcfedr उत्तर पढ़ने में परेशानी होती, लेकिन वह स्पष्ट रूप से बताता है कि XDebug वास्तव में धीमा हो जाता है array_map;)
igorsantos07

मेरे पास Xhprof के उपयोग array_mapऔर परीक्षण का प्रदर्शन है foreach। और इसकी दिलचस्प array_map`फोरच` की तुलना में अधिक मेमोरी खाती है।
गोपाल जोशी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.