¿Qué significa rendimiento en PHP?

Resuelto Gordon asked hace 54 años • 9 respuestas

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 yieldpalabra 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 yieldpalabra clave? ¿Es incluso PHP válido? Y si es así, ¿cómo lo uso?

Gordon avatar Jan 01 '70 08:01 Gordon
Aceptado

Qué es yield?

La yieldpalabra 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 => 29 => 10

También puedes controlar $keyel foreachusando

yield $someKey => $someValue;

En la función generadora, $someKeyes lo que quieras que aparezca $keyy $someValuees 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 rangela 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 rangePHP, lo ejecutaremos, crearemos la matriz completa de números en la memoria y returnesa matriz completa en el foreachbucle que luego la revisará y generará los valores. En otras palabras, foreachoperará en la propia matriz. La rangefunció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 yieldpalabra 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 xrangetiene un forbucle, se ejecutará y cederá hasta que $maxse alcance. Piense en ello como si el foreachgenerador 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 yieldpalabra 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 yielduna 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
Gordon avatar Jul 05 '2013 07:07 Gordon

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.

tsusanka avatar Apr 24 '2017 19:04 tsusanka

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 yieldpalabra 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).

CSSBurner avatar May 14 '2020 15:05 CSSBurner

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#
Think Big avatar Sep 28 '2016 12:09 Think Big