En primer lugar, entiendo que en el 90% de las aplicaciones la diferencia de rendimiento es completamente irrelevante, pero solo necesito saber cuál es la construcción más rápida. Eso y ...
La información actualmente disponible sobre ellos en la red es confusa. Mucha gente dice que foreach es malo, pero técnicamente debería ser más rápido, ya que se supone que simplifica la escritura de un recorrido de matriz usando iteradores. Los iteradores, que nuevamente se supone que son más rápidos, pero en PHP también aparentemente son muy lentos (¿o no es esto una cosa de PHP?). Estoy hablando de las funciones de matriz: next () prev () reset () etc. bueno, si son incluso funciones y no una de esas características del lenguaje PHP que parecen funciones.
Para reducir esto un poco : no me interesa atravesar matrices en pasos de más de 1 (tampoco hay pasos negativos, es decir, iteración inversa). Tampoco estoy interesado en un recorrido hacia y desde puntos arbitrarios, solo 0 a la longitud. Tampoco veo la manipulación de matrices con más de 1000 claves sucediendo de forma regular, ¡pero sí veo una matriz atravesada varias veces en la lógica de una aplicación! También en cuanto a las operaciones, en gran parte solo la manipulación de cuerdas y el eco.
Aquí hay algunos sitios de referencia:
http://www.phpbench.com/
http://www.php.lt/benchmark/phpbench.php
Lo que escucho en todas partes:
foreach
es lento, y por lo tantofor
/while
es más rápido- PHP
foreach
copia la matriz sobre la que itera; para hacerlo más rápido necesitas usar referencias - código como este: es más rápido que un
$key = array_keys($aHash); $size = sizeOf($key);
for ($i=0; $i < $size; $i++)foreach
Este es mi problema. Escribí este script de prueba: http://pastebin.com/1ZgK07US y no importa cuántas veces ejecute el script, obtengo algo como esto:
foreach 1.1438131332397
foreach (using reference) 1.2919359207153
for 1.4262869358063
foreach (hash table) 1.5696921348572
for (hash table) 2.4778981208801
En breve:
foreach
es más rápido queforeach
con referenciaforeach
es más rápido quefor
foreach
es más rápido quefor
para una tabla hash
¿Alguien puede explicarlo?
- ¿Estoy haciendo algo mal?
- ¿PHP para cada cosa de referencia realmente está haciendo una diferencia? Quiero decir, ¿por qué no lo copiaría si pasa por referencia?
- ¿Cuál es el código de iterador equivalente para la instrucción foreach? He visto algunos en la red, pero cada vez que los pruebo, el tiempo está muy lejos; También probé algunas construcciones de iteradores simples, pero parece que nunca obtengo resultados decentes: ¿los iteradores de matriz en PHP son simplemente horribles?
- ¿Hay formas / métodos / construcciones más rápidas para iterar a través de una matriz que no sea FOR / FOREACH (y WHILE)?
Versión de PHP 5.3.0
Editar: Respuesta Con la ayuda de la gente de aquí, pude juntar las respuestas a todas las preguntas. Los resumiré aquí:
- "¿Estoy haciendo algo mal?" El consenso parece ser: sí, no puedo usar el eco en los puntos de referencia. Personalmente, todavía no veo cómo echo es alguna función con tiempo aleatorio de ejecución o cómo cualquier otra función es de alguna manera diferente; eso y la capacidad de ese script para generar exactamente los mismos resultados de foreach mejor que todo es difícil para explicar aunque solo "estás usando echo" (bueno, ¿qué debería haber estado usando). Sin embargo, reconozco que la prueba debería hacerse con algo mejor; aunque no se me ocurre un compromiso ideal.
- "¿PHP para cada cosa de referencia realmente hace una diferencia? Quiero decir, ¿por qué no lo copiaría si pasa por referencia? ircmaxell muestra que sí, las pruebas adicionales parecen demostrar que en la mayoría de los casos la referencia debería ser más rápida, aunque dado mi fragmento de código anterior, definitivamente no significa todo. Acepto que el problema probablemente no sea demasiado intuitivo para molestarse en ese nivel y requeriría algo extremo, como la descompilación, para determinar cuál es mejor para cada situación.
- "¿Cuál es el código de iterador equivalente para la declaración foreach? He visto algunos en la red, pero cada vez que los pruebo, el tiempo está muy lejos. También probé algunas construcciones de iterador simples pero nunca parece obtener resultados decentes - ¿Son los iteradores de matriz en PHP simplemente horribles? " ircmaxell proporcionó la respuesta a continuación; aunque el código solo puede ser válido para la versión PHP> = 5
- "¿Hay formas / métodos / construcciones más rápidas para iterar a través de una matriz que no sea FOR / FOREACH (y WHILE)?" Gracias a Gordon por la respuesta. El uso de nuevos tipos de datos en PHP5 debería mejorar el rendimiento o la memoria (cualquiera de los cuales podría ser deseable según su situación). Si bien en cuanto a la velocidad, muchos de los nuevos tipos de arreglos no parecen ser mejores que arreglo (), splpriorityqueue y splobjectstorage parecen ser sustancialmente más rápidos. Enlace proporcionado por Gordon: http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/
Gracias a todos los que intentaron ayudar.
Probablemente me quedo con foreach (la versión sin referencia) para cualquier recorrido simple.
Respuestas:
Mi opinión personal es utilizar lo que tenga sentido en el contexto. Personalmente, casi nunca uso
for
para el recorrido de la matriz. Lo uso para otros tipos de iteraciones, peroforeach
es demasiado fácil ... La diferencia de tiempo será mínima en la mayoría de los casos.Lo más importante a tener en cuenta es:
for ($i = 0; $i < count($array); $i++) {
Es un ciclo caro, ya que las llamadas cuentan con cada iteración. Mientras no estés haciendo eso, no creo que realmente importe ...
En cuanto a la referencia que marca la diferencia, PHP usa copy-on-write, por lo que si no escribe en la matriz, habrá relativamente poca sobrecarga durante el bucle. Sin embargo, si comienza a modificar la matriz dentro de la matriz, ahí es donde comenzará a ver las diferencias entre ellas (ya que se necesitará copiar toda la matriz y la referencia puede simplemente modificar en línea) ...
En cuanto a los iteradores,
foreach
es equivalente a:$it->rewind(); while ($it->valid()) { $key = $it->key(); // If using the $key => $value syntax $value = $it->current(); // Contents of loop in here $it->next(); }
En la medida en que haya formas más rápidas de iterar, realmente depende del problema. Pero realmente necesito preguntar, ¿por qué? Entiendo que quiera hacer las cosas más eficientes, pero creo que está perdiendo el tiempo con una microoptimización. Recuerda
Premature Optimization Is The Root Of All Evil
...Editar: Basado en el comentario, decidí hacer una ejecución de referencia rápida ...
$a = array(); for ($i = 0; $i < 10000; $i++) { $a[] = $i; } $start = microtime(true); foreach ($a as $k => $v) { $a[$k] = $v + 1; } echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => &$v) { $v = $v + 1; } echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => $v) {} echo "Completed in ", microtime(true) - $start, " Seconds\n"; $start = microtime(true); foreach ($a as $k => &$v) {} echo "Completed in ", microtime(true) - $start, " Seconds\n";
Y los resultados:
Completed in 0.0073502063751221 Seconds Completed in 0.0019769668579102 Seconds Completed in 0.0011849403381348 Seconds Completed in 0.00111985206604 Seconds
Entonces, si está modificando la matriz en el ciclo, es varias veces más rápido usar referencias ...
Y la sobrecarga solo para la referencia es en realidad menor que copiar la matriz (esto está en 5.3.2) ... Entonces parece (al menos en 5.3.2) como si las referencias fueran significativamente más rápidas ...
fuente
"the better standard way to adopt."
rendimiento de @srcspider no es el único criterio para elegir qué adoptar. especialmente en un caso tan descabellado. Francamente, solo está perdiendo el tiempoNo estoy seguro de que esto sea tan sorprendente. La mayoría de las personas que codifican en PHP no están bien informadas sobre lo que PHP está haciendo en realidad en condiciones básicas. Declararé algunas cosas, que serán ciertas la mayor parte del tiempo:
Si no está modificando la variable, el valor por valor es más rápido en PHP. Esto se debe a que su referencia se cuenta de todos modos y el valor por valor le da menos que hacer. Sabe que en el momento en que modificas ese ZVAL (la estructura de datos interna de PHP para la mayoría de los tipos), tendrá que romperlo de una manera sencilla (cópialo y olvídate del otro ZVAL). Pero nunca lo modificas, así que no importa. Las referencias lo hacen más complicado con más contabilidad que tiene que ver para saber qué hacer cuando modifica la variable. Entonces, si es de solo lectura, paradójicamente es mejor no señalar eso con &. Lo sé, es contrario a la intuición, pero también es cierto.
Foreach no es lento. Y para una iteración simple, la condición contra la que está probando - "estoy al final de esta matriz" - se realiza utilizando código nativo, no códigos de operación PHP. Incluso si se trata de códigos de operación en caché de APC, sigue siendo más lento que un montón de operaciones nativas realizadas en el metal desnudo.
El uso de un bucle for "para ($ i = 0; $ i <count ($ x); $ i ++) es lento debido a count () y la falta de capacidad de PHP (o realmente cualquier lenguaje interpretado) para evaluar en parse tiempo si algo modifica la matriz. Esto evita que evalúe el recuento una vez.
Pero incluso una vez que lo arregla con "$ c = count ($ x); para ($ i = 0; $ i <$ c; $ i ++) el $ i <$ c es un montón de opcodes Zend en el mejor de los casos, como es el $ i ++. En el curso de 100000 iteraciones, esto puede importar. Foreach sabe a nivel nativo qué hacer. No se necesitan códigos de operación PHP para probar la condición "estoy al final de esta matriz".
¿Qué pasa con las cosas de la vieja escuela "while (list ("? Bueno, usar cada (), current (), etc.todos van a involucrar al menos una llamada de función, que no es lenta, pero no es gratuita. Sí, esos ¡son códigos de operación PHP de nuevo! Entonces, while + list + cada uno tiene sus costos también.
Por estas razones, foreach es comprensiblemente la mejor opción para una iteración simple.
Y no lo olvide, también es el más fácil de leer, por lo que es beneficioso para todos.
fuente
Una cosa a tener en cuenta en los puntos de referencia (especialmente phpbench.com) es que aunque los números son sólidos, las pruebas no lo son. Muchas de las pruebas en phpbench.com están haciendo cosas que son triviales y abusan de la capacidad de PHP de almacenar en caché las búsquedas de matrices para sesgar los puntos de referencia o, en el caso de iterar sobre una matriz, en realidad no se prueba en casos del mundo real (nadie escribe vacío para bucles). Hice mis propios puntos de referencia que descubrí que reflejan bastante los resultados del mundo real y siempre muestran la sintaxis iterativa nativa del idioma
foreach
en la parte superior (sorpresa, sorpresa).//make a nicely random array $aHash1 = range( 0, 999999 ); $aHash2 = range( 0, 999999 ); shuffle( $aHash1 ); shuffle( $aHash2 ); $aHash = array_combine( $aHash1, $aHash2 ); $start1 = microtime(true); foreach($aHash as $key=>$val) $aHash[$key]++; $end1 = microtime(true); $start2 = microtime(true); while(list($key) = each($aHash)) $aHash[$key]++; $end2 = microtime(true); $start3 = microtime(true); $key = array_keys($aHash); $size = sizeOf($key); for ($i=0; $i<$size; $i++) $aHash[$key[$i]]++; $end3 = microtime(true); $start4 = microtime(true); foreach($aHash as &$val) $val++; $end4 = microtime(true); echo "foreach ".($end1 - $start1)."\n"; //foreach 0.947947025299 echo "while ".($end2 - $start2)."\n"; //while 0.847212076187 echo "for ".($end3 - $start3)."\n"; //for 0.439476966858 echo "foreach ref ".($end4 - $start4)."\n"; //foreach ref 0.0886030197144 //For these tests we MUST do an array lookup, //since that is normally the *point* of iteration //i'm also calling noop on it so that PHP doesn't //optimize out the loopup. function noop( $value ) {} //Create an array of increasing indexes, w/ random values $bHash = range( 0, 999999 ); shuffle( $bHash ); $bstart1 = microtime(true); for($i = 0; $i < 1000000; ++$i) noop( $bHash[$i] ); $bend1 = microtime(true); $bstart2 = microtime(true); $i = 0; while($i < 1000000) { noop( $bHash[$i] ); ++$i; } $bend2 = microtime(true); $bstart3 = microtime(true); foreach( $bHash as $value ) { noop( $value ); } $bend3 = microtime(true); echo "for ".($bend1 - $bstart1)."\n"; //for 0.397135972977 echo "while ".($bend2 - $bstart2)."\n"; //while 0.364789962769 echo "foreach ".($bend3 - $bstart3)."\n"; //foreach 0.346374034882
fuente
Es 2020 y las cosas han evolucionado enormemente con php 7.4 y opcache .
Aquí está el punto de referencia OP ^, ejecutado como CLI de Unix , sin las partes echo y html.
La prueba se ejecutó localmente en una computadora normal.
php -v PHP 7.4.6 (cli) (built: May 14 2020 10:02:44) ( NTS )
Secuencia de comandos de referencia modificada:
<?php ## preperations; just a simple environment state $test_iterations = 100; $test_arr_size = 1000; // a shared function that makes use of the loop; this should // ensure no funny business is happening to fool the test function test($input) { //echo '<!-- '.trim($input).' -->'; } // for each test we create a array this should avoid any of the // arrays internal representation or optimizations from getting // in the way. // normal array $test_arr1 = array(); $test_arr2 = array(); $test_arr3 = array(); // hash tables $test_arr4 = array(); $test_arr5 = array(); for ($i = 0; $i < $test_arr_size; ++$i) { mt_srand(); $hash = md5(mt_rand()); $key = substr($hash, 0, 5).$i; $test_arr1[$i] = $test_arr2[$i] = $test_arr3[$i] = $test_arr4[$key] = $test_arr5[$key] = $hash; } ## foreach $start = microtime(true); for ($j = 0; $j < $test_iterations; ++$j) { foreach ($test_arr1 as $k => $v) { test($v); } } echo 'foreach '.(microtime(true) - $start)."\n"; ## foreach (using reference) $start = microtime(true); for ($j = 0; $j < $test_iterations; ++$j) { foreach ($test_arr2 as &$value) { test($value); } } echo 'foreach (using reference) '.(microtime(true) - $start)."\n"; ## for $start = microtime(true); for ($j = 0; $j < $test_iterations; ++$j) { $size = count($test_arr3); for ($i = 0; $i < $size; ++$i) { test($test_arr3[$i]); } } echo 'for '.(microtime(true) - $start)."\n"; ## foreach (hash table) $start = microtime(true); for ($j = 0; $j < $test_iterations; ++$j) { foreach ($test_arr4 as $k => $v) { test($v); } } echo 'foreach (hash table) '.(microtime(true) - $start)."\n"; ## for (hash table) $start = microtime(true); for ($j = 0; $j < $test_iterations; ++$j) { $keys = array_keys($test_arr5); $size = sizeOf($test_arr5); for ($i = 0; $i < $size; ++$i) { test($test_arr5[$keys[$i]]); } } echo 'for (hash table) '.(microtime(true) - $start)."\n";
Salida:
foreach 0.0032877922058105 foreach (using reference) 0.0029420852661133 for 0.0025191307067871 foreach (hash table) 0.0035080909729004 for (hash table) 0.0061779022216797
Como puede ver, la evolución es una locura, aproximadamente 560 veces más rápido que lo informado en 2012.
En mis máquinas y servidores, siguiendo mis numerosos experimentos, los conceptos básicos de los bucles son los más rápidos. Esto es aún más claro usando bucles anidados ( $ i $ j $ k ..)
También es el más flexible en uso y tiene una mejor legibilidad desde mi punto de vista.
fuente
Creo, pero no estoy seguro: el
for
bucle toma dos operaciones para verificar e incrementar valores.foreach
carga los datos en la memoria, luego iterará todos los valores.fuente