una - php array_walk use
Rendimiento de foreach, array_map con lambda y array_map con función estática (3)
Es interesante ejecutar este punto de referencia con xdebug desactivado, ya que xdebug agrega bastante sobrecarga, especialmente para las llamadas de función.
Esta es la secuencia de comandos de FGM utilizando 5.6 con xdebug
ForEach : 0.79232501983643
MapClosure: 4.1082420349121
MapNamed : 1.7884571552277
Sin xdebug
ForEach : 0.69830799102783
MapClosure: 0.78584599494934
MapNamed : 0.85125398635864
Aquí solo hay una diferencia muy pequeña entre la versión foreach y la de cierre.
También es interesante agregar una versión con un cierre con un use
function useMapClosureI($numbers) {
$i = 10;
return array_map(function($number) use ($i) {
return $number * $i++;
}, $numbers);
}
Para la comparación agrego:
function useForEachI($numbers) {
$result = array();
$i = 10;
foreach ($numbers as $number) {
$result[] = $number * $i++;
}
return $result;
}
Aquí podemos ver que tiene un impacto en la versión de cierre, mientras que la matriz no ha cambiado notablemente.
19/11/2015 También he agregado resultados usando PHP 7 y HHVM para comparar. Las conclusiones son similares, aunque todo es mucho más rápido.
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
¿Cuál es la diferencia de rendimiento (si hay alguna) entre estos tres enfoques, ambos utilizados para transformar una matriz en otra matriz?
- Usando
foreach
- Usando
array_map
con la función lambda / closure - Usando
array_map
con función / método "estático" - ¿Hay algún otro enfoque?
Para aclararme, veamos los ejemplos, todos haciendo lo mismo: multiplicar la matriz de números por 10:
$numbers = range(0, 1000);
Para cada
$result = array();
foreach ($numbers as $number) {
$result[] = $number * 10;
}
return $result;
Mapa con lambda
return array_map(function($number) {
return $number * 10;
}, $numbers);
Mapa con función ''estática'', pasado como referencia de cadena
function tenTimes($number) {
return $number * 10;
}
return array_map(''tenTimes'', $numbers);
¿Hay algún otro enfoque? Estaré feliz de escuchar en realidad todas las diferencias entre los casos de arriba, y las entradas de por qué uno debe ser utilizado en lugar de otros.
Es interesante. Pero obtuve un resultado opuesto con los siguientes códigos, que se simplifican a partir de mis proyectos actuales:
// 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;
}
Aquí están mis datos y códigos de prueba:
$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);
}
El resultado es:
0.0098 : array_map 0.0114 : foreach 0.0114 : array_map_use_local 0.0115 : foreach_use_local
Mis pruebas fueron en el entorno de producción LAMP sin xdebug. I''am wandering xdebug ralentizaría el rendimiento de array_map.
FWIW, acabo de hacer el punto de referencia ya que el cartel no lo hizo. Ejecutando en PHP 5.3.10 + XDebug.
ACTUALIZACIÓN 2015-01-22 compare con la respuesta de mcfedr a continuación para obtener resultados adicionales sin XDebug y una versión PHP más reciente.
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";
}
Obtengo resultados bastante consistentes con números de 1M en una docena de intentos:
- Foreach: 0.7 segundos
- Mapa al cierre: 3.4 segundos
- Mapa en el nombre de la función: 1.2 seg.
Suponiendo que la deslucida velocidad del mapa en el cierre fue causada por el cierre posiblemente evaluado cada vez, también probé así:
function useMapClosure($numbers) {
$closure = function($number) {
return $number * 10;
};
return array_map($closure, $numbers);
}
Pero los resultados son idénticos, lo que confirma que el cierre solo se evalúa una vez.
2014-02-02 ACTUALIZACIÓN: descarga de opcodes
Aquí están los volcados de código de operación para las tres devoluciones de llamada. First 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
Entonces 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
y el cierre que llama:
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
luego la función 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
y la función nombrada a la que llama, _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