PHP Conference Kansai 2025

Limpieza de Ciclos

Tradicionalmente, los mecanismos de conteo de referencias, como los utilizados anteriormente en PHP, no saben manejar las fugas de memoria debidas a referencias circulares; sin embargo, desde PHP 5.3.0, un algoritmo síncrono derivado del análisis » Concurrent Cycle Collection in Reference Counted Systems se utiliza para abordar este problema en particular.

Una explicación completa del funcionamiento del algoritmo iría un poco más allá del alcance de esta sección, pero aquí presentaremos los principios básicos. En primer lugar, estableceremos algunas reglas básicas. Si un refcount se incrementa, el contenedor siempre se utiliza, por lo tanto, no se limpia. Si el refcount se decrementa y llega a cero, el contenedor zval puede ser eliminado y la memoria liberada. En primer lugar, esto significa que los ciclos perturbadores solo pueden crearse cuando el refcount se decrementa a un valor diferente de cero. Luego, en un ciclo problemático, es posible detectar la basura verificando si es posible o no decrementar su refcount en uno, verificando luego qué zvals tienen un refcount a cero.

Algoritmo de recolección de basura

Para evitar tener que llamar a la rutina de limpieza en cada decrementación de refcount posible, el algoritmo coloca todas las raíces zval en un "búfer de raíces" (marcándolas en "violeta"). También se asegura de que cada raíz aparezca solo una vez en el búfer. El mecanismo de limpieza solo interviene cuando el búfer está lleno. Vea el paso A en la figura anterior.

En el paso B, el algoritmo lanza una búsqueda en todas las raíces posibles, para decrementar en una unidad los refcounts de todas las zvals que encuentra, teniendo mucho cuidado de no decrementar dos veces el refcount de la misma zval (marcándolas como "grises"). En el paso C, el algoritmo relanza una búsqueda en todas las raíces posibles y escanea el valor de refcount de cada zval. Si encuentra un refcount a cero, la zval se marca como "blanca" (azul en la figura). Si encuentra un valor superior a cero, cancela la decrementación del refcount realizando una búsqueda desde este nodo, y las marca como "negras" nuevamente. En el último paso, D, el algoritmo recorre todo el búfer de raíces y las elimina, escaneando cada zval; cualquier zval marcada como "blanca" en el paso anterior será entonces eliminada de la memoria.

Ahora que se sabe globalmente cómo funciona el algoritmo, se verá cómo se ha integrado en PHP. Por omisión, el recolector de basura de PHP está activado. Sin embargo, hay una opción de php.ini para cambiar esto: zend.enable_gc.

Cuando el recolector de basura está activado, el algoritmo de búsqueda de ciclos descrito anteriormente se ejecuta cada vez que el búfer está lleno. El búfer de raíces tiene un tamaño fijado a 10.000 raíces (este parámetro es modificable gracias a GC_THRESHOLD_DEFAULT en Zend/zend_gc.c en el código fuente de PHP, por lo tanto, se necesita una recompilación). Si el recolector de basura está desactivado, la búsqueda de ciclos también lo está. Sin embargo, las raíces posibles siempre se registrarán en el búfer, esto no depende de la activación del recolector de basura.

Si el búfer está lleno mientras el mecanismo de limpieza está desactivado, las raíces ya no se registrarán. Estas raíces nunca serán analizadas por el algoritmo, y si formaban parte de referencias circulares, nunca se limpiarán, y causarán fugas de memoria.

La razón por la que las raíces posibles se registran en el búfer incluso si el mecanismo está desactivado es que habría sido demasiado costoso verificar la posible activación del mecanismo en cada intento de agregar una raíz al búfer. El mecanismo de recolección de basura y análisis puede, por su parte, ser muy costoso en tiempo.

Además de poder cambiar el valor del parámetro de configuración zend.enable_gc, también se puede activar o desactivar el mecanismo de recolección de basura llamando a las funciones gc_enable() o gc_disable() respectivamente. Utilizar estas funciones tendrá el mismo efecto que modificar el parámetro de configuración. También se tiene la posibilidad de forzar la ejecución del recolector de basura en un momento dado en el script, incluso si el búfer aún no está completamente lleno. Para ello, utilice la función gc_collect_cycles(), que devolverá el número de ciclos recolectados.

Se puede tomar el control desactivando el recolector de basura o forzándolo a pasar en un momento dado porque algunas partes de la aplicación podrían depender fuertemente del tiempo de procesamiento, en cuyo caso se podría desear que el recolector de basura no se inicie. Por supuesto, al desactivar el recolector de basura para algunas partes de la aplicación, se corre el riesgo de crear fugas de memoria, ya que algunas raíces probables podrían no registrarse en el búfer de memoria de tamaño limitado. En consecuencia, generalmente se recomienda desencadenar manualmente el proceso gracias a gc_collect_cycles() justo antes de la llamada a gc_disable(), para liberar memoria. Esto dejará un búfer vacío, y habrá más espacio para raíces probables cuando el mecanismo esté desactivado.

add a note

User Contributed Notes 3 notes

up
17
Dallas
7 years ago
After testing, breaking up memory intensive code into a separate function allows the garbage collection to work.

For example the original code was like:-
while(true){
//do memory intensive code
}

can be turned into something like:-
function intensive($parameters){
//do memory intensive code
}

while(true){
intensive($parameters);
}
up
13
Yousha dot A at Hotmail dot com
10 years ago
Memory leak: meaning you keep a reference to it thus preventing the GC from collecting it.
up
10
Yousha dot A at Hotmail dot com
8 years ago
── Unused Objects ─── ─ In use Objects
↓ ↓ ↓
_____________________________________
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
▲ ▲
Unreferenced Referenced
Objects Objects

█ Memory leak
To Top