157 votes

Performances de foreach, array_map avec lambda et array_map avec fonction statique

Quelle est la différence de performance (s'il y en a une) entre ces trois approches, toutes deux utilisées pour transformer un tableau en un autre tableau ?

  1. Utilisation de foreach
  2. Utilisation de array_map avec fonction lambda/fermeture
  3. Utilisation de array_map avec une fonction/méthode "statique
  4. Y a-t-il une autre approche ?

Pour être plus clair, regardons les exemples, qui font tous la même chose - multiplier le tableau de chiffres par 10 :

$numbers = range(0, 1000);

Foreach

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

Carte avec lambda

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

Carte avec fonction 'static', passée comme référence de chaîne de caractères.

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

Existe-t-il une autre approche ? Je serai heureux de l'entendre. todo les différences entre les cas ci-dessus, et toute contribution expliquant pourquoi l'un d'eux devrait être utilisé plutôt que les autres.

256voto

mcfedr Points 1193

Il est intéressant d'exécuter ce benchmark avec xdebug désactivé, car xdebug ajoute beaucoup de surcharge, en particulier aux appels de fonction.

Voici l'exécution du script de FGM en utilisant 5.6 Avec xdebug

ForEach   : 0.79232501983643
MapClosure: 4.1082420349121
MapNamed  : 1.7884571552277

Sans xdebug

ForEach   : 0.69830799102783
MapClosure: 0.78584599494934
MapNamed  : 0.85125398635864

Ici, il n'y a qu'une très petite différence entre la version foreach et la version closure.

Il est également intéressant d'ajouter une version avec une fermeture avec un bouton de commande. use

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

Pour comparaison, j'ajoute :

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

Ici, nous pouvons voir qu'il a un impact sur la version de fermeture, alors que le tableau n'a pas changé de façon notable.

19/11/2015 J'ai aussi maintenant ajouté des résultats en utilisant PHP 7 et HHVM pour la comparaison. Les conclusions sont similaires, bien que tout soit beaucoup plus rapide.

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

131voto

FGM Points 1171

Pour info, j'ai juste fait le benchmark puisque le poster ne l'a pas fait. Fonctionne avec PHP 5.3.10 + XDebug.

MISE À JOUR 2015-01-22 comparer avec la réponse de mcfedr ci-dessous pour des résultats supplémentaires sans XDebug et une version plus récente de 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";
}

J'obtiens des résultats assez cohérents avec des numéros 1M sur une douzaine de tentatives :

  • Foreach : 0.7 sec
  • Carte sur la fermeture : 3.4 sec
  • Carte sur le nom de la fonction : 1,2 sec.

En supposant que la vitesse médiocre de la carte lors de la fermeture était due au fait que la fermeture pouvait être évaluée à chaque fois, j'ai également testé comme ceci :

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

  return array_map($closure, $numbers);
}

Mais les résultats sont identiques, ce qui confirme que la fermeture n'est évaluée qu'une seule fois.

2014-02-02 MISE À JOUR : vidage des opcodes

Voici les codes d'opération pour les trois rappels. Premier 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

Ensuite, le 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

et la fermeture qu'elle appelle :

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

alors le useMapNamed() fonction :

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

et la fonction nommée qu'elle appelle, _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

23voto

elzorro Points 346

Voici quelques tests mis à jour pour la version actuelle de PHP 8 (RC2). Nous avons également ajouté des fermetures courtes

PHP 8.0 RC2

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

8voto

Clarence Points 721

C'est intéressant. Mais j'ai obtenu un résultat opposé avec les codes suivants qui sont simplifiés à partir de mes projets actuels :

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

Voici mes données de test et mes 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);
}

Le résultat est :

0.0098 : array\_map
0.0114 : foreach
0.0114 : array\_map\_use\_local
0.0115 : foreach\_use\_local

Mes tests étaient dans un environnement de production LAMP sans xdebug. Je pense que xdebug ralentirait les performances de array_map.

0voto

Kha Tran Points 1

J'ai essayé de tester le code de @FGM sur PHP 8 et window 10 en 10 fois. Et voici le résultat : Image

Je ne sais pas si PHP peut avoir du JIT. Je suppose qu'il avait le JIT en PHP8 car dans le fichier php.ini, j'ai vu une commande de configuration : auto_globals_jit=On.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X