¿Qué significa rendimiento en PHP?
Recientemente me encontré con este código:
function xrange($min, $max)
{
for ($i = $min; $i <= $max; $i++) {
yield $i;
}
}
Nunca antes había visto esta yield
palabra clave. Intentando ejecutar el código que obtengo
Error de análisis: error de sintaxis, T_VARIABLE inesperado en la línea x
Entonces, ¿cuál es esta yield
palabra clave? ¿Es incluso PHP válido? Y si es así, ¿cómo lo uso?
Qué es yield
?
La yield
palabra clave devuelve datos de una función generadora:
El corazón de una función generadora es la palabra clave de rendimiento. En su forma más simple, una declaración de rendimiento se parece mucho a una declaración de retorno, excepto que en lugar de detener la ejecución de la función y regresar, rendimiento proporciona un valor al código que recorre el generador y pausa la ejecución de la función del generador.
¿Qué es una función generadora?
Una función generadora es efectivamente una forma más compacta y eficiente de escribir un Iterador . Le permite definir una función (su xrange
) que calculará y devolverá valores mientras la recorre :
function xrange($min, $max) {
for ($i = $min; $i <= $max; $i++) {
yield $i;
}
}
[…]
foreach (xrange(1, 10) as $key => $value) {
echo "$key => $value", PHP_EOL;
}
Esto crearía el siguiente resultado:
0 => 1
1 => 2
…
9 => 10
También puedes controlar $key
el foreach
usando
yield $someKey => $someValue;
En la función generadora, $someKey
es lo que quieras que aparezca $key
y $someValue
es el valor en $val
. En el ejemplo de la pregunta eso es $i
.
Tenga en cuenta que, internamente, las claves enteras secuenciales se emparejan con los valores obtenidos, al igual que con una matriz no asociativa. Incluso podemos establecer valores de rendimiento con claves.
¿Cuál es la diferencia con las funciones normales?
Ahora quizás se pregunte por qué no utilizamos simplemente range
la función nativa de PHP para lograr ese resultado. Y tienes razón. La salida sería la misma. La diferencia es cómo llegamos allí.
Cuando usamos range
PHP, lo ejecutaremos, crearemos la matriz completa de números en la memoria y return
esa matriz completa en el foreach
bucle que luego la revisará y generará los valores. En otras palabras, foreach
operará en la propia matriz. La range
función y la foreach
única "habla" una vez. Piense en ello como recibir un paquete por correo. El repartidor te entregará el paquete y se marchará. Y luego desenvuelves todo el paquete, sacando lo que haya dentro.
Cuando usamos la función generadora, PHP ingresará a la función y la ejecutará hasta que llegue al final o a una yield
palabra clave. Cuando se encuentra con a yield
, devolverá el valor en ese momento al bucle externo. Luego regresa a la función generadora y continúa desde donde cedió. Dado que xrange
tiene un for
bucle, se ejecutará y cederá hasta que $max
se alcance. Piense en ello como si el foreach
generador y el generador estuvieran jugando al ping pong.
¿Por qué necesito eso?
Obviamente, los generadores se pueden utilizar para solucionar los límites de memoria. Dependiendo de su entorno, hacer un range(1, 1000000)
fatal su secuencia de comandos, mientras que lo mismo con un generador funcionará bien. O como dice Wikipedia:
Debido a que los generadores calculan sus valores obtenidos sólo según demanda, son útiles para representar secuencias que serían costosas o imposibles de calcular de una vez. Estos incluyen, por ejemplo, secuencias infinitas y flujos de datos en vivo.
También se supone que los generadores son bastante rápidos. Pero hay que tener en cuenta que cuando hablamos de rapidez, normalmente hablamos en cantidades muy pequeñas. Entonces, antes de salir corriendo y cambiar todo su código para usar generadores, haga una evaluación comparativa para ver dónde tiene sentido.
Otro caso de uso de generadores son las corrutinas asincrónicas. La yield
palabra clave no sólo devuelve valores sino que también los acepta. Para obtener detalles sobre esto, consulte las dos excelentes publicaciones de blog vinculadas a continuación.
¿ Desde cuándo puedo usar yield
?
Los generadores se han introducido en PHP 5.5 . Si intenta utilizar yield
una versión anterior a esa versión, se producirán varios errores de análisis, según el código que sigue a la palabra clave. Entonces, si recibe un error de análisis de ese código, actualice su PHP.
Fuentes y lecturas adicionales:
- Documentos oficiales
- El RFC original
- Blog de kelunik: Introducción a los generadores
- Blog de ircmaxell: Qué pueden hacer los generadores por usted
- Blog de NikiC: Multitarea cooperativa usando corrutinas en PHP
- Multitarea PHP cooperativa
- ¿Cuál es la diferencia entre un generador y una matriz?
- Wikipedia sobre Generadores en general
Esta función utiliza rendimiento:
function a($items) {
foreach ($items as $item) {
yield $item + 1;
}
}
Es casi igual que este sin:
function b($items) {
$result = [];
foreach ($items as $item) {
$result[] = $item + 1;
}
return $result;
}
La única diferencia es que a()
devuelve un generador y b()
solo una matriz simple. Puedes iterar en ambos.
Además, el primero no asigna una matriz completa y, por lo tanto, requiere menos memoria.
Ninguna de las respuestas muestra un ejemplo concreto que utilice matrices masivas pobladas por miembros no numéricos. A continuación se muestra un ejemplo que utiliza una matriz generada explode()
en un archivo .txt grande (262 MB en mi caso de uso):
<?php
ini_set('memory_limit','1000M');
echo "Starting memory usage: " . memory_get_usage() . "<br>";
$path = './file.txt';
$content = file_get_contents($path);
foreach(explode("\n", $content) as $ex) {
$ex = trim($ex);
}
echo "Final memory usage: " . memory_get_usage();
El resultado fue:
Starting memory usage: 415160
Final memory usage: 270948256
Ahora compárelo con un script similar, usando la yield
palabra clave:
<?php
ini_set('memory_limit','1000M');
echo "Starting memory usage: " . memory_get_usage() . "<br>";
function x() {
$path = './file.txt';
$content = file_get_contents($path);
foreach(explode("\n", $content) as $x) {
yield $x;
}
}
foreach(x() as $ex) {
$ex = trim($ex);
}
echo "Final memory usage: " . memory_get_usage();
El resultado de este script fue:
Starting memory usage: 415152
Final memory usage: 415616
Claramente, los ahorros en el uso de memoria fueron considerables (ΔMemoryUsage -----> ~270,5 MB en el primer ejemplo, ~450B en el segundo ejemplo).
ejemplo sencillo
<?php
echo '#start main# ';
function a(){
echo '{start[';
for($i=1; $i<=9; $i++)
yield $i;
echo ']end} ';
}
foreach(a() as $v)
echo $v.',';
echo '#end main#';
?>
producción
#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#
ejemplo avanzado
<?php
echo '#start main# ';
function a(){
echo '{start[';
for($i=1; $i<=9; $i++)
yield $i;
echo ']end} ';
}
foreach(a() as $k => $v){
if($k === 5)
break;
echo $k.'=>'.$v.',';
}
echo '#end main#';
?>
producción
#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#