CakeFest 2024: The Official CakePHP Conference

Consideraciones acerca del Rendimiento

Como mencionamos en la sección anterior, la recolección de raíces tiene muy bajo impacto en el rendimiento, pero aquí es cuando comparamos PHP 5.2 contra PHP 5.3. Si bien la recolección de posibles raíces comparado con la no recolección, como en PHP 5.2, es más lenta, hay otras modificaciones en tiempo de ejecución en PHP 5.3 que impiden que esta pérdida de rendimiento en particular pueda siquiera apreciarse.

Hay dos principales sectores en los que el rendimiento se ve afectado. El primero es el uso reducido de memoria, y mientras que el segunda es la reducción en tiempo de ejecución cuando el mecanismo recolector de basura lleva a cabo la limpieza de memoria. Revisaremos estos dos asuntos.

Uso Reducido de Memoria

Antes de nada, la razón por la que se implementa el mecanismo recolector de basuras es para reducir el uso de memoria limpiando, una vez que se cumplen las condiciones, las variables de referencias circulares. En la implementación de PHP, esto sucede cuando el buffer raíz está lleno, o cuando se invoca la función gc_collect_cycles(). En el gráfico mostrado abajo, se muestra el uso de memoria tanto en PHP 5.2 como en 5.3, excluyendo la memoria base que el propio PHP ocupa al arrancar.

Ejemplo #1 Ejemplo de uso de memoria

<?php
class Foo
{
public
$var = '3.14159265359';
}

$baseMemory = memory_get_usage();

for (
$i = 0; $i <= 100000; $i++ )
{
$a = new Foo;
$a->self = $a;
if (
$i % 500 === 0 )
{
echo
sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n";
}
}
?>
Comparación de uso de memoria entre PHP 5.2 y PHP 5.3

En este ejemplo didáctico, estamos creando un objeto en el que una propiedad enlaza de nuevo al propio objeto. Cuando la variable $a del script se reasigna en la siguiente iteración del bucle, típicamente ocurriría una fuga de memoria. En este caso, se fugan dos contenedores zval (el zval del objeto, y el zval de la propiead), pero sólo se encuentra una posible raíz: la variable que se desasignó. Tras 10.000 iteraciones, el buffer se llena (con un total de 10.000 posibles raíces), y se lanza el mecanismo recolector de basura y libera la memoria asociada con esas posibles raíces. Puede apreciarse claramente en el uso de memoria "dentado" de la gráfica para PHP 5.3. Tras las 10.000 iteraciones, el mecanismo libera la memoria asociada a las variables con referencias cíclicas. En este ejemplo, el propio mecanismo no debe hacer un gran trabajo, puesto que la estructura que produce la fuga es extremadamente sencilla. A partir del diagrama, se puede comprobar que el uso máximo de memoria en PHP 5.3 es en torno a 9 Mb, mientras que en PHP 5.2 el uso de memoria no para de aumentar.

Reducción en Tiempo de Ejecución

El segundo sector en el que el mecanismo recolector de basura influye en el rendimiento es en el tiempo que lleva a éste liberar la memoria "fugada". Para comprobar de cuánto estamos hablando, modificaremos ligeramente el script anterior para tener en cuenta un mayor número de iteraciones, y eliminaremos las figuras de uso de memoria intermedio. Este es el segundo script:

Ejemplo #2 Influencia en rendimiento de Recolector de Basuras

<?php
class Foo
{
public
$var = '3.14159265359';
}

for (
$i = 0; $i <= 1000000; $i++ )
{
$a = new Foo;
$a->self = $a;
}

echo
memory_get_peak_usage(), "\n";
?>

Ejecutaremos dos veces este script, una con el ajuste zend.enable_gc habilitado, y en la otra deshabilitado:

Ejemplo #3 Ejecutando el script anterior

time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
# and
time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php

En la máquina de ejemplo, el primer comando parece llevar en torno a 10,7 segundos, mientras que al segundo comando le lleva 11,4. Esto es un incremento de en torno al 7%. Sin embargo, el uso máximo de memoria del script se ha reducido en un 98%, pasando de 931Mb a 10Mb. Esta prueba no es muy científica, ni siquiera representativa de aplicaciones reales, pero demuestra que el uso de memoria se beneficia del mecanismo recolector de basuras. Lo interesante es que para este script en particular el incremento es siempre del 7%, mientras que el ahorro de memoria aumenta a medida que se encuentran más referencias cíclicas en la ejecución del script.

Estadísticas Internas de PHP del Recolector de Basuras

Todavía es posible dar más información sobre cómo funciona el mecanismo recolector de basuras dentro de PHP. Pero para hacerlo, será necesario recompilar PHP para habilitar el código de análises y de recopilación de datos. Se tendrá que asignar a la variable de entorno CFLAGS el valor -DGC_BENCH=1 antes de ejecutar ./configure con las opciones deseadas. La siguiente secuencia muestra cómo hacerlo:

Ejemplo #4 Recompilando PHP para habilitar el análisis del Recolector de Basuras

export CFLAGS=-DGC_BENCH=1
./config.nice
make clean
make

Al ejecutar el ejemplo que vimos arriba con el nuevo binario de PHP que hemos creado, veremos que se muestra el siguiente resultado tras la ejecución de PHP:

Ejemplo #5 Estadísticas de Recolección de Basuras

GC Statistics
-------------
Runs:               110
Collected:          2072204
Root buffer length: 0
Root buffer peak:   10000

      Possible            Remove from  Marked
        Root    Buffered     buffer     grey
      --------  --------  -----------  ------
ZVAL   7175487   1491291    1241690   3611871
ZOBJ  28506264   1527980     677581   1025731

Las estadísticas más informativas son las que se muestran en el primer bloque. Puede comprobarse que el mecanismo recolector de basuras se ejecutó 110 veces, y en total, se liberaron más de 2 millones de ubicaciones en memoria durante esas 110 ejecuciones. Puesto que el mecanismo recolector de ciclos se ha ejecutado al menos una vez, el "pico del buffer raíz" es siempre 10.000.

Conclusión

En general el recolector de basuras de PHP sólo provocará un retraso cuando el algoritmo recolector de ciclos funcione, mientras que en scripts normales (más pequeños) no habrá un impacto real en el rendimiento.

Sin embargo, en el caso en el que el mecanismo recolector de ciclos se ejecute para scripts normales, la reducción de memoria permitirá que puedan funcionar más scripts concurrentemente en el servidor, ya que en total no utilizarán mucha memoria.

Los beneficios son más evidentss en scripts de larga duración, tales como grandes suits de pruebas o scripts demonios. También en las aplicaciones » PHP-GTK, que generalmente suelen ejecutarse durante más tiempo que scripts para la Web; el nuevo mecanismo marcará la diferencia en cuanto a las fugas de memoria progresivas en el tiempo.

add a note

User Contributed Notes 2 notes

up
22
Talisman
8 years ago
The GC, unfortunately, as expounded in the examples above, has the tendency to promote lazy programming.
Clearly the benefits of the GC to assist in memory management are there, and help to maintain a stable system, but it is no excuse to not plan and test your code properly.
Always re-read your code critically and objectively to ensure that you are not introducing memory leaks unintentionally.
up
19
Dmitry dot Balabka at gmail dot com
5 years ago
There is a possibility to get GC performance stats without PHP recompilation. Starting from Xdebug version 2.6 you are able to enable stats collection into the file (default dir /tmp with name gcstats.%p):

php -dxdebug.gc_stats_enable=1 your_script.php
To Top