Quelle est la différence de performance (s’il en existe) entre ces trois approches, toutes deux utilisées pour transformer un tableau en un autre?
foreach
array_map
avec fonction lambda/fermeturearray_map
avec fonction/méthode 'statique'Pour que tout soit clair, regardons les exemples et agissons tous de la même manière, en multipliant le tableau de nombres 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 la fonction 'statique', passée comme référence de chaîne
function tenTimes($number) {
return $number * 10;
}
return array_map('tenTimes', $numbers);
Y a-t-il une autre approche? Je serai heureux d'entendre réellement tous différences entre les cas d'en haut, et toutes les entrées pourquoi on devrait être utilisé à la place des autres.
FWIW, je viens de faire le point de repère depuis que l'affiche ne l'a pas fait. En cours d'exécution sur PHP 5.3.10 + XDebug.
MISE À JOUR 2015-01-22 comparer avec la réponse de mcfedr ci-dessous pour obtenir des résultats supplémentaires sans XDebug et une version plus récente 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 1 million de chiffres sur une douzaine de tentatives:
En supposant que la fermeture de la carte soit évaluée à chaque fois, la rapidité médiocre de la carte à la fermeture a également été testée. 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, confirmant que la fermeture n’est évaluée qu’une fois.
MISE À JOUR DU 2014-02-02: dump des opcodes
Voici les vidages d'opcode pour les trois rappels. Première 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
Puis la 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 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
puis la fonction 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
et la fonction nommée qu'il 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
Il est intéressant de lancer cette référence avec xdebug désactivé, car xdebug ajoute beaucoup de temps système, notamment aux appels de fonction.
Ceci est le script de FGM exécuté avec 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 fermeture.
C'est également intéressant d'ajouter une version avec une fermeture avec un 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;
}
Nous pouvons voir ici que cela a un impact sur la version de fermeture, alors que le tableau n'a pas sensiblement changé.
19/11/2015 J'ai également ajouté des résultats avec PHP 7 et HHVM à des fins de 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
C'est intéressant. Mais j'ai un résultat opposé avec les codes suivants, qui sont simplifiés par rapport à 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 et codes de test:
$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 suis errant xdebug ralentirait les performances de array_map.