Foreach, lambda ile array_map ve statik işlevli array_map performansı


144

Her ikisi de bir diziyi başka bir diziye dönüştürmek için kullanılan bu üç yaklaşım arasındaki performans farkı (varsa) nedir?

  1. kullanma foreach
  2. array_mapLambda / kapatma fonksiyonu ile kullanma
  3. Kullanılması array_map'statik' işlevi / yöntemi ile
  4. Başka bir yaklaşım var mı?

Kendimi açıklığa kavuşturmak için, hepsi aynı olan örneklere bakalım - sayı dizisini 10 ile çarpalım:

$numbers = range(0, 1000);

Her biri için

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

Lambda ile eşleştirin

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

Dize başvurusu olarak iletilen 'statik' işlevli harita

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

Başka bir yaklaşım var mı? Aslında , yukarıdaki vakalar arasındaki tüm farklılıkları ve neden diğerleri yerine birinin kullanılması gerektiğine dair tüm girdileri duymaktan mutluluk duyacağım .


10
Neden sadece kıyaslamıyor ve ne olduğunu görmüyorsunuz?
Jon

17
Ben bir kıyaslama yapabilirim. Ama yine de dahili olarak nasıl çalıştığını bilmiyorum. Birinin daha hızlı olduğunu öğrensem bile, nedenini hala bilmiyorum. PHP sürümü nedeniyle mi? Verilere bağlı mı? İlişkilendirilebilir ve sıradan diziler arasında bir fark var mı? Tabii ki bütün kriterleri yapabilirim ama bazı teoriler edinmek burada çok zaman kazandırıyor. Umarım anlarsın ...
Pavel S.

2
Geç yorum, ancak (list ($ k, $ v) = her biri ($ dizi)) yukarıdakilerin hepsinden daha hızlı değil mi? Ben php5.6 bunu karşılaştırmak değil, ama önceki sürümlerinde oldu.
Owen Beresford

Yanıtlar:


121

FWIW, Ben poster yapmadım çünkü ben sadece ölçüt yaptım. PHP 5.3.10 + XDebug üzerinde çalışıyor.

UPDATE 2015-01-22, XDebug ve daha yeni bir PHP sürümü olmadan ek sonuçlar için mcfedr'in aşağıdaki cevabı ile karşılaştırın.


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

Bir düzine denemede 1 milyon sayı ile oldukça tutarlı sonuçlar elde ediyorum:

  • Foreach: 0,7 saniye
  • Kapanıştaki harita: 3.4 sn
  • İşlev adında harita: 1,2 sn.

Haritanın kapanıştaki cansız hızının, muhtemelen her seferinde değerlendirilen kapanmadan kaynaklandığını varsayarsak, ayrıca şu şekilde test ettim:


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

  return array_map($closure, $numbers);
}

Ancak sonuçlar özdeştir ve kapanışın sadece bir kez değerlendirildiğini doğrular.

2014-02-02 GÜNCELLEME: opcodes dökümü

İşte üç geri arama için opcode dökümleri. İlk olarak 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

Sonra 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

ve kapattığı kapanış:


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

sonra useMapNamed()işlevi:


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

ve adlandırdığı işlev _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

Karşılaştırmalar için teşekkürler. Ancak, neden böyle bir fark olduğunu bilmek istiyorum. Bir fonksiyon çağrısı yükü nedeniyle mi?
Pavel S.

4
Soruna opcode dökümlerini ekledim. Gördüğümüz ilk şey, adlandırılmış işlev ve kapatmanın tam olarak aynı dökümü olduğu ve sadece bir istisna dışında array_map ile aynı şekilde çağrıldıklarıdır: kapatma çağrısı, bir neden daha açık olduğunu bildiren DECLARE_LAMBDA_FUNCTION, bunun neden kullanıldığını açıklar adlandırılmış işlevi kullanmaktan biraz daha yavaştır. Şimdi, dizi döngüsü ile array_map çağrıları karşılaştırıldığında, dizi döngüsünde her şey bir işleve çağrı yapılmadan satır içi olarak yorumlanır, yani push / pop için bağlam yok, sadece döngünün sonunda bir JMP vardır, bu da büyük farkı açıklar. .
FGM

4
Ben sadece yerleşik bir işlev (strtolower) kullanarak denedim ve bu durumda, useMapNamedaslında daha hızlıdır useArray. Bahsetmeye değer olduğunu düşündüm.
DisgruntledGoat

1
'De lap, range()ilk mikro çağrının üstündeki çağrıyı istemiyor musunuz? (Her ne kadar döngü zamanı ile karşılaştırıldığında muhtemelen önemsiz olsa da.)
contrebis

1
@billynoah PHP7.x gerçekten çok daha hızlı. Kod önbelleğe almanın yanı sıra çok sayıda optimizasyon yaptığı için özellikle bu sürüm tarafından oluşturulan opcodes'leri görmek ilginç olacaktır.
FGM

232

Xdebug işlev çağrılarına oldukça fazla ek yükü eklediğinden, bu ölçütü xdebug devre dışı bırakılmış olarak çalıştırmak ilginçtir.

Bu, 5.6 With xdebug kullanarak çalıştırılan FGM'nin komut dosyasıdır

ForEach   : 0.79232501983643
MapClosure: 4.1082420349121
MapNamed  : 1.7884571552277

Xdebug olmadan

ForEach   : 0.69830799102783
MapClosure: 0.78584599494934
MapNamed  : 0.85125398635864

Burada foreach ve closure versiyonu arasında çok küçük bir fark vardır.

Ayrıca bir kapaklı bir sürüm eklemek de ilginç use

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

Karşılaştırma için şunu ekliyorum:

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

Burada, kapatma sürümü üzerinde bir etki yaptığını görebiliriz, ancak dizi belirgin bir şekilde değişmedi.

19/11/2015 Şimdi karşılaştırma için PHP 7 ve HHVM kullanarak da sonuçlar ekledim. Sonuçlar benzer, ancak her şey çok daha hızlı.

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
Kravatını kırarak ve size 51. oyu vererek kazananı beyan ederim. Testin sonuçları değiştirmediğinden emin olmak ÇOK önemlidir! Ancak, "Dizi" için sonuç zamanlarınız foreach loop yöntemi, değil mi?
Buttle Butkus

2
Mükemmel bir cevap. 7'nin ne kadar hızlı olduğunu görmek güzel. Kişisel zamanımda kullanmaya başlamalıyım, hala iş yerinde 5.6.
Dan

1
Öyleyse neden foreach yerine array_map kullanmalıyız? Performansta kötüyse neden PHP'ye eklendi? Foreach yerine array_map gerektiren belirli bir koşul var mı? Foreach'ın işleyemediği ve array_map'in işleyebileceği belirli bir mantık var mı?
HendraWD

3
array_map(ve ilgili işlevler array_reduce, array_filter) güzel kod yazalım. Çok array_mapdaha yavaş olsaydı kullanmak için bir sebep olurdu foreach, ama çok benzer, bu yüzden array_mapmantıklı olan her yerde kullanacağım .
mcfedr

3
PHP7'yi görmek çok güzel. Projelerim için farklı bir arka uç diline geçmek üzereydim ama PHP'ye yapışacağım.
realnsleo

8

İlginç. Ama şu anki projelerimden basitleştirilmiş aşağıdaki kodlarla zıt bir sonuç aldım:

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

İşte test verilerim ve kodlarım:

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

Sonuç:

0.0098: dizi_dosyası
0.0114: foreach
0.0114: dizi_dosyası_kullanım_konumu
0.0115: foreach_use_local

Testlerim xdebug olmadan LAMP üretim ortamında yapıldı. Geziyorum xdebug dizi_map performansını yavaşlatır.


Eğer @mcfedr cevabı okumak için sorun vardı, ama o açıkça XDebug gerçekten yavaşlar açıklar emin değil array_map;)
igorsantos07

Ben performansını test var array_mapve foreachXhprof kullanarak. Ve ilginç array_mapolanı "foreach" ten daha fazla bellek tüketir.
Gopal Joshi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.