Leistung von foreach, array_map mit Lambda und array_map mit statischer Funktion

Lesezeit: 8 Minuten

Leistung von foreach array map mit Lambda und array map mit statischer
Paul S.

Was ist der Leistungsunterschied (falls vorhanden) zwischen diesen drei Ansätzen, die beide verwendet werden, um ein Array in ein anderes Array umzuwandeln?

  1. Verwenden foreach
  2. Verwenden array_map mit Lambda-/Schließfunktion
  3. Verwenden array_map mit ‘statischer’ Funktion/Methode
  4. Gibt es einen anderen Ansatz?

Um mich klar zu machen, schauen wir uns die Beispiele an, die alle dasselbe tun – das Array von Zahlen mit 10 multiplizieren:

$numbers = range(0, 1000);

Für jeden

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

Karte mit Lambda

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

Karte mit ‘statischer’ Funktion, übergeben als String-Referenz

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

Gibt es einen anderen Ansatz? Ich würde mich freuen, tatsächlich zu hören alle Unterschiede zwischen den Fällen von oben und alle Eingaben, warum einer anstelle von anderen verwendet werden sollte.

  • Warum machst du nicht einfach einen Benchmark und schaust, was passiert?

    – Jon

    9. August 2013 um 10:38 Uhr

  • Nun, ich kann einen Benchmark machen. Aber ich weiß immer noch nicht, wie es intern funktioniert. Selbst wenn ich herausfinde, dass einer schneller ist, weiß ich immer noch nicht warum. Liegt es an der PHP-Version? Liegt es an den Daten? Gibt es einen Unterschied zwischen assoziativen und gewöhnlichen Arrays? Natürlich kann ich eine ganze Reihe von Benchmarks erstellen, aber etwas Theorie spart hier viel Zeit. Ich hoffe, Sie verstehen…

    – Paul S.

    9. August 2013 um 14:21 Uhr

  • Später Kommentar, aber ist while( list($k, $v)= each($array)) nicht schneller als alle oben genannten? Ich habe dies in php5.6 nicht bewertet, aber es war in früheren Versionen.

    – Owen Beresford

    1. Juli 2015 um 17:33 Uhr


1646635448 690 Leistung von foreach array map mit Lambda und array map mit statischer
mcfedr

Es ist interessant, diesen Benchmark mit deaktiviertem xdebug auszuführen, da xdebug ziemlich viel Overhead hinzufügt, insbesondere bei Funktionsaufrufen.

Dies ist das Skript von FGM, das unter Verwendung von 5.6 mit xdebug ausgeführt wird

ForEach   : 0.79232501983643
MapClosure: 4.1082420349121
MapNamed  : 1.7884571552277

Ohne xdebug

ForEach   : 0.69830799102783
MapClosure: 0.78584599494934
MapNamed  : 0.85125398635864

Hier gibt es nur einen sehr kleinen Unterschied zwischen der Foreach- und der Closure-Version.

Es ist auch interessant, eine Version mit einem Verschluss mit a hinzuzufügen use

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

Zum Vergleich füge ich hinzu:

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

Hier können wir sehen, dass es sich auf die Closure-Version auswirkt, während sich das Array nicht merklich geändert hat.

19.11.2015 Ich habe jetzt auch Ergebnisse mit PHP 7 und HHVM zum Vergleich hinzugefügt. Die Schlussfolgerungen sind ähnlich, obwohl alles viel schneller ist.

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

  • Ich erkläre Sie zum Gewinner, indem ich das Unentschieden trenne und Ihnen die 51. positive Stimme gebe. SEHR wichtig, um sicherzustellen, dass der Test die Ergebnisse nicht verändert! Frage, aber Ihre Ergebniszeiten für “Array” sind die Foreach-Loop-Methode, oder?

    – Buttle Butkus

    2. Juli 2016 um 3:51 Uhr

  • Ausgezeichnete Antwort. Schön zu sehen, wie schnell 7 ist. Ich muss anfangen, es in meiner persönlichen Zeit zu benutzen, immer noch um 5,6 bei der Arbeit.

    – Dan

    19. Oktober 2016 um 22:48 Uhr

  • Warum müssen wir also array_map anstelle von foreach verwenden? Warum wurde PHP hinzugefügt, wenn die Leistung schlecht ist? Gibt es eine bestimmte Bedingung, die array_map anstelle von foreach benötigt? Gibt es eine bestimmte Logik, die foreach nicht verarbeiten kann und array_map verarbeiten kann?

    – HendraWD

    18. April 2017 um 13:13 Uhr


  • array_map (und die damit verbundenen Funktionen array_reduce, array_filter) lassen Sie schönen Code schreiben. Wenn array_map viel langsamer wäre, wäre es ein Grund zu verwenden foreachaber es ist sehr ähnlich, also werde ich verwenden array_map überall macht es Sinn.

    – mcfedr

    20. April 2017 um 8:01 Uhr


  • Schön zu sehen, dass PHP7 stark verbessert wurde. War kurz davor, für meine Projekte auf eine andere Backend-Sprache umzusteigen, aber ich bleibe bei PHP.

    – realnsleo

    11. November 2017 um 8:56 Uhr

1646635449 641 Leistung von foreach array map mit Lambda und array map mit statischer
Genitalverstümmelung

FWIW, ich habe gerade den Benchmark gemacht, da Poster es nicht gemacht hat. Läuft auf PHP 5.3.10 + XDebug.

UPDATE 2015-01-22 vergleichen Sie mit der Antwort von mcfedr unten für zusätzliche Ergebnisse ohne XDebug und eine neuere 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";
}

Ich bekomme ziemlich konsistente Ergebnisse mit 1M-Zahlen über ein Dutzend Versuche:

  • Foreach: 0,7 Sek
  • Karte beim Schließen: 3,4 Sek
  • Karte auf Funktionsname: 1,2 Sek.

Angenommen, die glanzlose Geschwindigkeit der Karte beim Schließen wurde dadurch verursacht, dass das Schließen möglicherweise jedes Mal ausgewertet wird, habe ich auch so getestet:


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

  return array_map($closure, $numbers);
}

Die Ergebnisse sind jedoch identisch, was bestätigt, dass der Verschluss nur einmal bewertet wird.

2014-02-02 UPDATE: Opcodes-Dump

Hier sind die Opcode-Dumps für die drei Callbacks. Zuerst 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

Dann ist die 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

und die Schließung, die es nennt:


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

dann ist die useMapNamed() Funktion:


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

und die benannte Funktion, die es aufruft, _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

  • Danke für die Benchmarks. Allerdings würde ich gerne wissen, warum es solche Unterschiede gibt. Liegt es an einem Funktionsaufruf-Overhead?

    – Paul S.

    25. Januar 2014 um 13:06 Uhr

  • Ich habe die Opcode-Dumps in der Ausgabe hinzugefügt. Das erste, was wir sehen können, ist, dass die benannte Funktion und die Closure genau denselben Dump haben und sie auf ziemlich dieselbe Weise über array_map aufgerufen werden, mit nur einer Ausnahme: Der Closure-Aufruf enthält einen weiteren Opcode DECLARE_LAMBDA_FUNCTION, was erklärt, warum es so ist etwas langsamer als die benannte Funktion. Wenn Sie jetzt die Array-Schleife mit den Aufrufen von array_map vergleichen, wird alles in der Array-Schleife inline interpretiert, ohne dass eine Funktion aufgerufen wird, was bedeutet, dass kein Kontext zum Pushen/Pop, nur ein JMP am Ende der Schleife, was wahrscheinlich den großen Unterschied erklärt .

    – Genitalverstümmelung

    2. Februar 2014 um 17:35 Uhr


  • Ich habe dies gerade mit einer integrierten Funktion (strtolower) versucht, und in diesem Fall useMapNamed ist eigentlich schneller als useArray. Fand das erwähnenswert.

    – Verärgerte Ziege

    30. August 2014 um 16:57 Uhr

  • Im lapwillst du das nicht range() Anruf über dem ersten Microtime-Anruf? (Obwohl wahrscheinlich unbedeutend im Vergleich zur Zeit für die Schleife.)

    – contrebis

    27. Januar 2015 um 15:53 ​​Uhr

  • @billynoah PHP7.x ist in der Tat so viel schneller. Es wäre interessant, die von dieser Version generierten Opcodes zu sehen, insbesondere im Vergleich mit/ohne Opcache, da neben dem Code-Caching viele Optimierungen vorgenommen werden.

    – Genitalverstümmelung

    14. Januar 2017 um 16:04 Uhr

Leistung von foreach array map mit Lambda und array map mit statischer
Elzorro

Hier sind einige aktualisierte Tests für die aktuelle Version von PHP 8 (RC2). Auch kurze Verschlüsse hinzugefügt

PHP 8.0 RC2

Foreach:         0.093745978673299
MapClosure:      0.096948345502218
MapShortClosure: 0.096264243125916
MapNamed:        0.091399153073629
MapClosureI:     0.11352666219076
ForEachI:        0.097501540184021

  • Danke! JIT aktiviert oder nicht?

    – Thomas Bachem

    7. März 2021 um 22:10 Uhr

1646635450 36 Leistung von foreach array map mit Lambda und array map mit statischer
Clarence

Es ist interessant. Aber ich habe ein gegenteiliges Ergebnis mit den folgenden Codes, die aus meinen aktuellen Projekten vereinfacht wurden:

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

Hier sind meine Testdaten und 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 < 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);
}

Das Ergebnis ist:

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

Meine Tests wurden in der LAMP-Produktionsumgebung ohne xdebug durchgeführt. Ich gehe davon aus, dass xdebug die Leistung von array_map verlangsamen würde.

963680cookie-checkLeistung von foreach, array_map mit Lambda und array_map mit statischer Funktion

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy