mirror of
https://github.com/php/doc-es.git
synced 2026-03-27 00:42:10 +01:00
git-svn-id: https://svn.php.net/repository/phpdoc/es/trunk@338251 c90b9560-bf6c-de11-be94-00142212c4b1
685 lines
27 KiB
XML
685 lines
27 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- $Revision$ -->
|
|
<!-- EN-Revision: f09b8f712b4b353243b7ad22ee55ea7825049a61 Maintainer: seros Status: ready -->
|
|
<!-- Reviewed: no Maintainer: seros -->
|
|
<chapter xml:id="features.gc" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
<title>Recolección de basura</title>
|
|
|
|
<para>
|
|
Esta sección explica las ventajas del nuevo mecanismo de Recoleción de basura (GC por sus siglas
|
|
en inglés de «Garbage Collection») de la versión 5.3 de PHP.
|
|
</para>
|
|
|
|
<sect1 xml:id="features.gc.refcounting-basics">
|
|
<title>Introducción al contador de referencias</title>
|
|
<para>
|
|
Una variable en PHP se almacena en un contenedor llamado "zval". Un contenedor zval
|
|
contiene, además del tipo de la variable y su valor, dos bits adicionales de
|
|
información. Al primero se le llama "is_ref" y contiene un valor booleano
|
|
que indica si la variable es parte o no de un "conjunto de referencias". Con
|
|
este bit, el motor de PHP sabe diferenciar entre variables normales
|
|
y referencias. Puesto que PHP permite referencias definidas por el usuario, tal y
|
|
como se crean con el operador &, un contenedor zval tiene también un mecanismo
|
|
contador de referencias para optimizar el uso de memoria. Esta segunda pieza adicional de
|
|
información, llamada "refcount", contiene el número de variables (también llamadas
|
|
símbolos) que apuntan a este contenedor zval. Todos los símbolos se almacenan en
|
|
una tabla de símbolos, de las cuales hay una por cada ámbito. Hay un ámbito para el
|
|
script principal (es decir, el solicitado por el navegador), además de uno por
|
|
cada función o método.
|
|
</para>
|
|
<para>
|
|
Se crea un contenedor zval al crear una nueva variable con un valor constante,
|
|
como por ejemplo:
|
|
<example>
|
|
<title>Creación de un nuevo contenedor zval</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$a = "nuevo string";
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
En este caso, el nombre del nuevo símbolo, <literal>a</literal>, se crea en el ámbito actual
|
|
y se crea un nuevo contenedor de variable con el tipo <type>string</type> y el valor
|
|
<literal>nuevo string</literal>. El bit "is_ref" se establece por omisión a &false; dado que no se
|
|
ha creado ninguna referencia en el espacio del usuario. "refcount" se establece a <literal>1</literal>, pues
|
|
solo hay un símbolo que haga uso de este contenedor de variable. Tenga en cuenta
|
|
que si "refcount" es <literal>1</literal>, "is_ref" siempre valdrá &false;. Si tiene <link
|
|
xlink:href="&url.xdebug;">Xdebug</link> instalado, puede mostrar esta
|
|
información llamando a <function>xdebug_debug_zval</function>.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Mostrar información de zval</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
xdebug_debug_zval('a');
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
a: (refcount=1, is_ref=0)='nuevo string'
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Al asignar esta variable a otro nombre de variable, se incrementará refcount.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Incremento de refcount de un zval</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$a = "nuevo string";
|
|
$b = $a;
|
|
xdebug_debug_zval( 'a' );
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
a: (refcount=2, is_ref=0)='nuevo string'
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Aquí, refcount vale <literal>2</literal>, pues el mismo contenedor de variable está
|
|
vinculado tanto por <varname>a</varname> como por <varname>b</varname>.
|
|
PHP es lo suficiente inteligente para no copiar el contenedor de variable
|
|
en sí cuando no es necesario. Los contenedores de variables se destruyen cuando
|
|
"refcount" llega a cero. "refcount" se decrementa en uno cuando alguno de los
|
|
símbolos vinculados al contenedor de variable abandona su ámbito (p.ej. cuando
|
|
finaliza una función) o cuando se llama a <function>unset</function> con un símbolo.
|
|
El siguiente ejemplo muestra esto:
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Decremento de refcount de zval</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$a = "nuevo string";
|
|
$c = $b = $a;
|
|
xdebug_debug_zval( 'a' );
|
|
unset( $b, $c );
|
|
xdebug_debug_zval( 'a' );
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
a: (refcount=3, is_ref=0)='nuevo string'
|
|
a: (refcount=1, is_ref=0)='nuevo string'
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Si ahora llamáramos a <literal>unset($a);</literal>, el contenedor de variable, incluyendo tanto
|
|
el tipo como el valor, se eliminarían de la memoría.
|
|
</para>
|
|
|
|
<sect2 xml:id="features.gc.compound-types">
|
|
<title>Tipos compuestos</title>
|
|
|
|
<para>
|
|
Las cosas se complican con tipos compuestos tales como <type>array</type>s y
|
|
<type>object</type>. En lugar de un valor de tipo <type>scalar</type>, los <type>array</type>s
|
|
y <type>object</type>s almacenan sus
|
|
propiedades en su propia tabla de símbolos. Esto significa que el siguiente
|
|
ejemplo crea tres contenedores zval:
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Crear un zval de tipo <type>array</type></title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$a = array( 'meaning' => 'life', 'number' => 42 );
|
|
xdebug_debug_zval( 'a' );
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs.similar;
|
|
<screen>
|
|
<![CDATA[
|
|
a: (refcount=1, is_ref=0)=array (
|
|
'meaning' => (refcount=1, is_ref=0)='life',
|
|
'number' => (refcount=1, is_ref=0)=42
|
|
)
|
|
]]>
|
|
</screen>
|
|
<para>Gráficamente</para>
|
|
<mediaobject>
|
|
<alt>Los zval de un array simple</alt>
|
|
<imageobject>
|
|
<imagedata fileref="en/features/figures/simple-array.png" format="PNG"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Los tres contenedores zval son: <varname>a</varname>, <varname>meaning</varname>, y <varname>number</varname>.
|
|
Se aplican reglas similares a la hora de incrementar y decrementar "refcounts". Abajo, añadimos otro
|
|
elemento al array, y fijamos su valor al contenido de un elemento ya
|
|
existente:
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Añadir un elemento existente a un array</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$a = array( 'meaning' => 'life', 'number' => 42 );
|
|
$a['life'] = $a['meaning'];
|
|
xdebug_debug_zval( 'a' );
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs.similar;
|
|
<screen>
|
|
<![CDATA[
|
|
a: (refcount=1, is_ref=0)=array (
|
|
'meaning' => (refcount=2, is_ref=0)='life',
|
|
'number' => (refcount=1, is_ref=0)=42,
|
|
'life' => (refcount=2, is_ref=0)='life'
|
|
)
|
|
]]>
|
|
</screen>
|
|
<para>Gráficamente</para>
|
|
<mediaobject>
|
|
<alt>Los zval de un array simple con una referencia</alt>
|
|
<imageobject>
|
|
<imagedata fileref="en/features/figures/simple-array2.png" format="PNG"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
A partir de la salida de Xdebug, vemos que tanto el antiguo como el nuevo elemento
|
|
del array apuntan a un contenedor zval cuyo "refcount" vale
|
|
<literal>2</literal>. Pese a que Xdebug muestra dos contenedores zval
|
|
con valor <literal>'life'</literal>, son el mismo. La función
|
|
<function>xdebug_debug_zval</function> no muestra esto, aunque
|
|
podria comprobarse mostrando también el puntero de memoria.
|
|
</para>
|
|
<para>
|
|
Eliminar un elemento del array es como eliminar un símbolo de un ámbito.
|
|
Al hacerlo, el "refcount" del contenedor al que apunta el elemento del array
|
|
se decrementa. De nuevo, cuando "refcount" alcanza cero, el contenedor
|
|
de la variable se elimina de la memoria. Un ejemplo que muestra esto:
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Eliminar un elemento de un array</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$a = array( 'meaning' => 'life', 'number' => 42 );
|
|
$a['life'] = $a['meaning'];
|
|
unset( $a['meaning'], $a['number'] );
|
|
xdebug_debug_zval( 'a' );
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs.similar;
|
|
<screen>
|
|
<![CDATA[
|
|
a: (refcount=1, is_ref=0)=array (
|
|
'life' => (refcount=1, is_ref=0)='life'
|
|
)
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Ahora, las cosas se vuelven interesantes si añadimos al propio array como
|
|
elemento del array, como veremos en el siguiente ejemplo, en el que también
|
|
usaremos el operador de referencia, ya que de lo contrario PHP crearía una copia:
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Añadir el propio array como elemento de sí mismo</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
$a = array( 'one' );
|
|
$a[] =& $a;
|
|
xdebug_debug_zval( 'a' );
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs.similar;
|
|
<screen>
|
|
<![CDATA[
|
|
a: (refcount=2, is_ref=1)=array (
|
|
0 => (refcount=1, is_ref=0)='one',
|
|
1 => (refcount=2, is_ref=1)=...
|
|
)
|
|
]]>
|
|
</screen>
|
|
<para>Gráficamente</para>
|
|
<mediaobject>
|
|
<alt>Los zval para un array que contiene una referencia circular</alt>
|
|
<imageobject>
|
|
<imagedata fileref="en/features/figures/loop-array.png" format="PNG"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Puede verse que tanto la variable de tipo array (<varname>a</varname>) como el segundo elemento
|
|
(<varname>1</varname>) apuntan ahora a un contenedor de variable que tiene un "refcount" de
|
|
<literal>2</literal>. Los "..." mostrados arriba indican que hay una referencia cíclica, lo cual,
|
|
por supuesto, significa que en este caso los "..." apuntan al array
|
|
original.
|
|
</para>
|
|
<para>
|
|
Al igual que antes, al eliminar una variable se elimina el símbolo y
|
|
el contador de referencias del contenedor de variable al que apunte se decrementa
|
|
en uno. De modo que, si eliminamos la variable <varname>$a</varname> después de ejecutar el código
|
|
anterior, el contador de referencias del contenedor de variable al que apuntan tanto <varname>$a</varname>
|
|
como el elemento "1" se decrementa en uno, de "2" a "1". Se puede representar así:
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Eliminar <varname>$a</varname></title>
|
|
<screen>
|
|
<![CDATA[
|
|
(refcount=1, is_ref=1)=array (
|
|
0 => (refcount=1, is_ref=0)='one',
|
|
1 => (refcount=1, is_ref=1)=...
|
|
)
|
|
]]>
|
|
</screen>
|
|
<para>Gráficamente</para>
|
|
<mediaobject>
|
|
<alt>Los zval después de eliminar un array con referencia circular mostrando la fuga de memoria</alt>
|
|
<imageobject>
|
|
<imagedata fileref="en/features/figures/leak-array.png" format="PNG"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
</example>
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 xml:id="features.gc.cleanup-problems">
|
|
<title>Problemas de limpieza</title>
|
|
<para>
|
|
Pese a que ya no hay ningún símbolo en ningún ámbito que apunte a esta
|
|
estructura, esta no se puede limpiar ya que el elemento "1" del array todavía
|
|
apunta al mismo array. Al no haber un símbolo externo que apunte a
|
|
él, no hay forma por la que el usuario pueda eliminar esta estructura; por
|
|
tanto tenemos una fuga de memoria. Afortunadamente, PHP limpiará esta estructura de datos al
|
|
finalizar la petición, aunque hasta entonces esté ocupando un valioso espacio de
|
|
memoria. Esta situación ocurre a menudo si se está implementando un algoritmo
|
|
de análisis o en otras situaciones en las que un nodo hijo apunte de nuevo a un elemento
|
|
"padre". Por supuesto, esta situación también puede suceder con objetos, donde
|
|
es más frecuente que ocurra, ya que los objetos siempre se usan implícitamente por
|
|
referencia.
|
|
</para>
|
|
<para>
|
|
Esto no debería ser un problema si sólo ocurre una o dos veces, pero si
|
|
estas fugas de memoria suceden miles, o incluso millones de veces, lógicamente
|
|
esto comenzaría a ser un problema. Es especialmente problemático en scripts
|
|
de larga duración, tales como demonios donde básicamente nunca terminan las peticiones,
|
|
o en un gran conjunto de pruebas unitarias. Esto último causó problemas al
|
|
ejecutar las pruebas unitarias del componente Template de la biblioteca
|
|
eZ Componentes. En algunos casos, podrían ser necesarios 2 GB de memoria que quizás no los
|
|
tenga el servidor de pruebas.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="features.gc.collecting-cycles">
|
|
<title>Recolección de referencias cíclicas</title>
|
|
<para>
|
|
Tradicionalmente, los mecanismos que contabilizan las referencias en memoria, tal como el que empleaba
|
|
PHP anteriormente, fallaban al abordar las fugas de memoria de referencias cíclicas.
|
|
Sin embargo, desde PHP 5.3.0 se implementa el algoritmo síncrono del artículo
|
|
<link xlink:href="&url.gc-paper;">Recolección de ciclos concurrentes en sistemas de contabilidad de referencias</link>
|
|
que resuelve este asunto.
|
|
</para>
|
|
<para>
|
|
Una explicación detallada del funcionamiento del algoritmo queda más allá del
|
|
objetivo de esta sección, aunque aquí explicaremos el mecanismo básico. Antes de nada,
|
|
debemos establecer unas reglas básicas. Si se incrementa un refcount,
|
|
este aún sigue en uso: no es basura. Si se decrementa el refcount y llega
|
|
a cero, el zval puede liberarse. Esto significa que la recolección de ciclos sólo
|
|
puede llevarse a cabo cuando un argumento refcount se decrementa a un valor que no sea cero.
|
|
En segundo lugar, en un ciclo de recolección de basura, es posible averiguar qué partes son
|
|
basura comprobando si se puede decrementar en uno sus refcount, para
|
|
después comprobar cuáles zval poseen un refcount de cero.
|
|
</para>
|
|
<para>
|
|
<mediaobject>
|
|
<alt>Algoritmo de recolección de basura</alt>
|
|
<imageobject>
|
|
<imagedata fileref="en/features/figures/gc-algorithm.png" format="PNG"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
</para>
|
|
<para>
|
|
Para evitar llamar a la comprobación de ciclos de basura en cada posible
|
|
decremento de un refcount, el algoritmo lo que hace es pasar todas las posibles raíces
|
|
(zvals) al "buffer raíz" (marcándolos en "púrpura"). También se asegura
|
|
de que cada raíz de basura sólo finaliza una vez en el buffer. Únicamente cuando el
|
|
buffer raíz está completo, comienza el mecanismo de recolección para todos los zval
|
|
diferentes que haya en su interior. Véase el paso A en la figura anterior.
|
|
</para>
|
|
<para>
|
|
En el paso B, el algoritmo inicia una primera búsqueda en profundidad de todas las posibles raíces
|
|
en las que decrementa por uno los refcount de los zval que encuentra, asegurándose de que no
|
|
decrementa dos veces el mismo zval (marcándolo en "gris"). En
|
|
el paso C, el algoritmo vuelve a llevar a cabo una búsqueda en profundidad dentro de cada nodo raíz,
|
|
para volver a comprobar el refcount de cada zval. Si ve que el refcount es
|
|
cero, se marca al zval en "blanco" (azul en la figura). Si es mayor que
|
|
cero, deshace el decremento con una búsqueda en profundidad partiendo de
|
|
ese punto, y se le vuelve a marcar en "negro". En el último
|
|
paso (D), el algoritmo recorre el buffer raíz eliminando las raíces zval que haya,
|
|
y a la vez, comprueba qué zvals se han marcado en "blanco" en
|
|
el paso anterior. Todos los zval marcados en "blanco" se eliminarán.
|
|
</para>
|
|
<para>
|
|
Ahora que ya tiene un conocimiento básico de cómo funciona el algoritmo,
|
|
volveremos atrás para ver cómo se integra esto en PHP. Por omisión, el recolector
|
|
de basuras de PHP está habilitado. Hay, sin embargo, una directiva en &php.ini;
|
|
que permite cambiar esto:
|
|
<link linkend="ini.zend.enable-gc">zend.enable_gc</link>.
|
|
</para>
|
|
<para>
|
|
Cuando el recolector de basura está habilitado, el algoritmo que busca ciclos,
|
|
tal y como se ha descrito arriba, se ejecuta cada vez que se llena el buffer raíz. Éste
|
|
tiene un tamaño fijo de 10.000 raíces posibles (se puede modificar
|
|
esto cambiando la contante <literal>GC_ROOT_BUFFER_MAX_ENTRIES</literal> en
|
|
<literal>Zend/zend_gc.c</literal> del código fuente de PHP, y recompilando
|
|
PHP). Cuando el recolector de basuras se deshabilita, no se ejecutará
|
|
el algoritmo que busca ciclos. Sea como fuere, las posibles raíces seguirían
|
|
registrándose en el buffer raíz, sin importar si el mecanismo recolector de basuras está
|
|
habilitado en la configuración o no.
|
|
</para>
|
|
<para>
|
|
Si estando deshabilitado el mecanismo recolector de basuras se llenara el buffer raíz
|
|
de posibles raíces, no se registraría al resto de raíces posibles, por lo que
|
|
no llegarían a ser analizadas por el algoritmo. Si fueran parte de un ciclo de
|
|
referencia circular, nunca se liberarían y podrían provocar una fuga de memoria.
|
|
</para>
|
|
<para>
|
|
La razón por la que se registran las posibles raíces estando deshabilitado
|
|
el mecanismo es porque es más rápido registrarlas que
|
|
comprobar en cada una de ellas si el mecanismo está habilitado.
|
|
Sin embargo, el recolector de basuras y el propio mecanismo de análisis, sí
|
|
puede conllevar una cantidad de tiempo considerable.
|
|
</para>
|
|
<para>
|
|
Ademas de poder cambiar la configuración <link linkend="ini.zend.enable-gc">zend.enable_gc</link>,
|
|
también es posible habilitar o deshabilitar el mecanismo recolector de basura
|
|
llamando a <function>gc_enable</function> o
|
|
<function>gc_disable</function> respectivamente. La llamada a estas funciones tiene
|
|
el mismo efecto que habilitar o deshabilitar el mecanismo en la propia configuración.
|
|
También es posible forzar la recolección de ciclos incluso sin que esté lleno el
|
|
buffer raíz. Para hacer esto, se puede usar la función
|
|
<function>gc_collect_cycles</function>. Esta función devuelve el número
|
|
de ciclos que fueron recolectados por el algoritmo.
|
|
</para>
|
|
<para>
|
|
El motivo por el que es posible habilitar o deshabilitar el mecanismo, o
|
|
iniciar los ciclos de recolección a mano, es porque podría haber determinadas
|
|
partes de una aplicación que necesiten mucha precisión de tiempo. En estos casos, quizás
|
|
no se desee que funcione el mecanismo recolector de basuras. Por supuesto, al deshabilitar
|
|
el recolector de basuras en algunas partes del código, se corre el riesgo de
|
|
provocar fugas de memoria, ya que algunas raíces podrían no caber en el buffer raíz.
|
|
Por tanto, lo mas prudente sería llamar a
|
|
<function>gc_collect_cycles</function> justo después de llamar a
|
|
<function>gc_disable</function> para que libere la memoria ocupada por
|
|
posibles raíces ya registradas en el buffer raíz. Esto deja por tanto
|
|
un buffer vacío, de modo que queda más espacio para almacenar
|
|
posibles raíces en el tiempo en que el mecanismo recolector de ciclos está deshabilitado.
|
|
</para>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="features.gc.performance-considerations">
|
|
<title>Consideraciones acerca del Rendimiento</title>
|
|
<para>
|
|
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.
|
|
</para>
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<sect2 xml:id="features.gc.performance-considerations.reduced-mem">
|
|
<title>Uso Reducido de Memoria</title>
|
|
<para>
|
|
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 <function>gc_collect_cycles</function>. 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.
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Ejemplo de uso de memoria</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?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";
|
|
}
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
<mediaobject>
|
|
<alt>Comparación de uso de memoria entre PHP 5.2 y PHP 5.3</alt>
|
|
<imageobject>
|
|
<imagedata fileref="en/features/figures/gc-benchmark.png" format="PNG"/>
|
|
</imageobject>
|
|
</mediaobject>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
En este ejemplo didáctico, estamos creando un objeto en el que una propiedad
|
|
enlaza de nuevo al propio objeto. Cuando la variable <varname>$a</varname>
|
|
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.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 xml:id="features.gc.performance-considerations.slowdowns">
|
|
<title>Reducción en Tiempo de Ejecución</title>
|
|
<para>
|
|
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:
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Influencia en rendimiento de Recolector de Basuras</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?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";
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
Ejecutaremos dos veces este script, una con el ajuste
|
|
<link linkend="ini.zend.enable-gc">zend.enable_gc</link> habilitado, y en la otra
|
|
deshabilitado:
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Ejecutando el script anterior</title>
|
|
<programlisting role="shell">
|
|
<![CDATA[
|
|
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
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
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.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 xml:id="features.gc.performance-considerations.internal-stats">
|
|
<title>Estadísticas Internas de PHP del Recolector de Basuras</title>
|
|
<para>
|
|
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 <literal>CFLAGS</literal>
|
|
el valor <literal>-DGC_BENCH=1</literal> antes de ejecutar
|
|
<literal>./configure</literal> con las opciones deseadas. La siguiente
|
|
secuencia muestra cómo hacerlo:
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Recompilando PHP para habilitar el análisis del Recolector de Basuras</title>
|
|
<programlisting role="shell">
|
|
<![CDATA[
|
|
export CFLAGS=-DGC_BENCH=1
|
|
./config.nice
|
|
make clean
|
|
make
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
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:
|
|
</para>
|
|
<para>
|
|
<example>
|
|
<title>Estadísticas de Recolección de Basuras</title>
|
|
<programlisting role="shell">
|
|
<![CDATA[
|
|
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
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
<para>
|
|
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.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 xml:id="features.gc.performance-considerations.conclusion">
|
|
<title>Conclusión</title>
|
|
<para>
|
|
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.
|
|
</para>
|
|
<para>
|
|
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.
|
|
</para>
|
|
<para>
|
|
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
|
|
<link xlink:href="&url.php.gtk;">PHP-GTK</link>, 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.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
</chapter>
|
|
|
|
<!-- Keep this comment at the end of the file
|
|
vim600: syn=xml fen fdm=syntax fdl=2 si
|
|
vim: et tw=78 syn=sgml
|
|
vi: ts=1 sw=1
|
|
-->
|