2009-07-17 14 views
15

Con la esperanza de tratar de evitar futuras fugas de memoria en programas php (módulos drupal, etc.) he estado jugando con scripts php simples que pierden memoria.¿Por qué esta simple secuencia de comandos php pierde memoria?

¿Podría un experto en php ayudarme a encontrar qué pasa con este script que hace que el uso de la memoria suba continuamente?

Pruebe a ejecutarlo usted mismo, cambiando varios parámetros. Los resultados son interesantes Aquí está:

<?php 

function memstat() { 
    print "current memory usage: ". memory_get_usage() . "\n"; 
} 

function waste_lots_of_memory($iters) { 
    $i = 0; 
    $object = new StdClass; 
    for (;$i < $iters; $i++) { 
    $object->{"member_" . $i} = array("blah blah blha" => 12345); 
    $object->{"membersonly_" . $i} = new StdClass; 
    $object->{"onlymember"} = array("blah blah blha" => 12345); 
    } 
    unset($object); 
} 

function waste_a_little_less_memory($iters) { 
    $i = 0; 
    $object = new StdClass; 
    for (;$i < $iters; $i++) { 

    $object->{"member_" . $i} = array("blah blah blha" => 12345); 
    $object->{"membersonly_" . $i} = new StdClass; 
    $object->{"onlymember"} = array("blah blah blha" => 12345); 

    unset($object->{"membersonly_". $i}); 
    unset($object->{"member_" . $i}); 
    unset($object->{"onlymember"}); 

    } 
    unset($object); 
} 

memstat(); 

waste_a_little_less_memory(1000000); 

memstat(); 

waste_lots_of_memory(10000); 

memstat(); 

Para mí, la salida es:

current memory usage: 73308 
current memory usage: 74996 
current memory usage: 506676 

[editado a más miembros de objetos no se ha establecido]

+0

Intentaré eliminar las líneas en el bucle for a la vez, para aislar el problema. –

Respuesta

34

unset() no libera la memoria utilizada por una variable. La memoria se libera cuando el "recolector de basura" (entre comillas, ya que PHP no tenía un recolector de basura real antes de la versión 5.3.0, solo una rutina libre de memoria que funcionaba principalmente en primitivas) lo consideraba apropiado.

Además, técnicamente, no debería necesitar llamar al unset() ya que la variable $object está limitada al alcance de su función.

Aquí hay una secuencia de comandos para demostrar la diferencia. Modifiqué su función memstat() para mostrar la diferencia de memoria desde la última llamada.

<?php 
function memdiff() { 
    static $int = null; 

    $current = memory_get_usage(); 

    if ($int === null) { 
     $int = $current; 
    } else { 
     print ($current - $int) . "\n"; 
     $int = $current; 
    } 
} 

function object_no_unset($iters) { 
    $i = 0; 
    $object = new StdClass; 

    for (;$i < $iters; $i++) { 
     $object->{"member_" . $i}= array("blah blah blha" => 12345); 
     $object->{"membersonly_" . $i}= new StdClass; 
     $object->{"onlymember"}= array("blah blah blha" => 12345); 
    } 
} 

function object_parent_unset($iters) { 
    $i = 0; 
    $object = new StdClass; 

    for (;$i < $iters; $i++) { 
     $object->{"member_" . $i}= array("blah blah blha" => 12345); 
     $object->{"membersonly_" . $i}= new StdClass; 
     $object->{"onlymember"}= array("blah blah blha" => 12345); 
    } 

    unset ($object); 
} 

function object_item_unset($iters) { 
    $i = 0; 
    $object = new StdClass; 

    for (;$i < $iters; $i++) { 

     $object->{"member_" . $i}= array("blah blah blha" => 12345); 
     $object->{"membersonly_" . $i}= new StdClass; 
     $object->{"onlymember"}= array("blah blah blha" => 12345); 

     unset ($object->{"membersonly_" . $i}); 
     unset ($object->{"member_" . $i}); 
     unset ($object->{"onlymember"}); 
    } 
    unset ($object); 
} 

function array_no_unset($iters) { 
    $i = 0; 
    $object = array(); 

    for (;$i < $iters; $i++) { 
     $object["member_" . $i] = array("blah blah blha" => 12345); 
     $object["membersonly_" . $i] = new StdClass; 
     $object["onlymember"] = array("blah blah blha" => 12345); 
    } 
} 

function array_parent_unset($iters) { 
    $i = 0; 
    $object = array(); 

    for (;$i < $iters; $i++) { 
     $object["member_" . $i] = array("blah blah blha" => 12345); 
     $object["membersonly_" . $i] = new StdClass; 
     $object["onlymember"] = array("blah blah blha" => 12345); 
    } 
    unset ($object); 
} 

function array_item_unset($iters) { 
    $i = 0; 
    $object = array(); 

    for (;$i < $iters; $i++) { 
     $object["member_" . $i] = array("blah blah blha" => 12345); 
     $object["membersonly_" . $i] = new StdClass; 
     $object["onlymember"] = array("blah blah blha" => 12345); 

     unset ($object["membersonly_" . $i]); 
     unset ($object["member_" . $i]); 
     unset ($object["onlymember"]); 
    } 
    unset ($object); 
} 

$iterations = 100000; 

memdiff(); // Get initial memory usage 

object_item_unset ($iterations); 
memdiff(); 

object_parent_unset ($iterations); 
memdiff(); 

object_no_unset ($iterations); 
memdiff(); 

array_item_unset ($iterations); 
memdiff(); 

array_parent_unset ($iterations); 
memdiff(); 

array_no_unset ($iterations); 
memdiff(); 
?> 

Si está utilizando objetos, asegúrese de que las clases implementa __unset() con el fin de permitir que unset() a los recursos adecuadamente claras. Trate de evitar tanto como sea posible el uso de clases de estructura variable como stdClass o la asignación de valores a los miembros que no se encuentran en su plantilla de clase, ya que la memoria asignada a esos no suele borrarse correctamente.

PHP 5.3.0 y superior tiene un mejor recolector de basura, pero está deshabilitado por defecto. Para habilitarlo, debe llamar al gc_enable() una vez.

+0

@mjgoins: edité mi publicación con más información. El colector de memoria predeterminado funciona mejor en tipos primitivos. Tan pronto como comiences a introducir recursos y objetos en la mezcla, comenzará a fallar. –

+0

Entonces la respuesta es hasta que php 5.3, php * no puede * ejecutar un trabajo arbitrariamente largo en el espacio de memoria fijo. Incluso mi función que pierde menos memoria se agotará lentamente, aunque con una alta asignación de memoria tomaría muchos días. – mjgoins

+0

@mjgoins: ejecuto varias tareas que pueden llevar de un minuto a varias horas. El truco es evitar objetos (o tratar de reutilizarlos si es necesario) y apegarse a primitivos (en su ejemplo, puede usar matrices fácilmente). –

2

Mi comprensión de memory_get_usage() es que su salida puede depender una amplia gama de factores de versión y sistema operativo.

Más importante aún, desarmar una variable no libera instantáneamente su memoria, desasignarla del proceso y devolverla al sistema operativo (de nuevo, las características de esta operación dependen del sistema operativo).

En resumen, es probable que necesite una configuración más complicada para detectar fugas de memoria.

0

No estoy seguro del funcionamiento exacto de la misma en PHP, pero en algunos otros idiomas un objeto que contiene otros objetos, cuando se establece en nulo, no establece inherentemente los otros objetos como nulos. Termina la referencia a esos objetos, pero como PHP no tiene "recolección de basura" en un sentido Java, los sub-objetos existen en la memoria hasta que se eliminan individualmente.

+0

En caso de que alguien se encuentre con esto, el proceso de liberar memoria cuando un objeto (u otro valor) no tiene referencias no se considera un "recolector de basura" en PHP porque no es un pieza separada de funcionalidad, pero eso no significa que no suceda. Las referencias a cualquier variable se cuentan, y 'unset()' etc. reduce ese conteo en uno; si el conteo se reduce a cero, la memoria se libera inmediatamente dentro del administrador de memoria PHP, lista para ser utilizada por una nueva variable. – IMSoP

3

memory_get_usage informa la cantidad de memoria que php ha asignado desde el sistema operativo. No necesariamente coincide con el tamaño de todas las variables en uso. Si php tiene un uso máximo de la memoria, puede decidir no devolver la cantidad de memoria no utilizada de inmediato. En su ejemplo, la función waste_a_little_less_memory desarma variables no utilizadas a lo largo del tiempo. Entonces el uso máximo es relativamente pequeño. El waste_lots_of_memory genera muchas variables (= mucha memoria usada) antes de desasignarlo. Entonces el uso máximo es mucho más grande.

22

memory_get_usage() "Devuelve la cantidad de memoria, en bytes, que actualmente está siendo asignado a su script PHP."

Esa es la cantidad de memoria asignada al proceso por el sistema operativo, no la cantidad de memoria utilizada por las variables asignadas. PHP no siempre libera la memoria al sistema operativo, pero esa memoria aún se puede reutilizar cuando se asignan nuevas variables.

Demostrando esto es simple. Cambiar el final de la secuencia de comandos para:

memstat(); 
waste_lots_of_memory(10000); 
memstat(); 
waste_lots_of_memory(10000); 
memstat(); 

Ahora, si estás en lo correcto, y PHP es en realidad una fuga de memoria, debería ver useage de memoria crecer dos veces. Sin embargo, aquí está el resultado real:

current memory usage: 88272 
current memory usage: 955792 
current memory usage: 955808 

Esto se debe a la memoria "liberado", después de la invocación inicial de waste_lots_of_memory() es re-utilizado por la segunda invocación.

En mis 5 años con PHP, he escrito scripts que han procesado millones de objetos y gigabytes de datos en un período de horas, y scripts que se han ejecutado durante meses. La administración de la memoria de PHP no es genial, pero no es tan mala como lo harías.

+0

Esta es la respuesta correcta a la pregunta y merece muchos más votos favorables de los que obtuvo. – IMSoP

0

memory_get_usage() no devuelve el uso inmediato de la memoria, pero almacena la memoria para ejecutar el proceso. en el caso de una gran matriz unset ($ array_a) no dará a conocer la memoria, pero consumir más de acuerdo con el memory_get_usage() en mi sistema ...

$array_a="(SOME big array)"; 
$array_b=""; 
//copy array_a to array_b 
for($line=0; $line<100;$line++){ 
$array_b[$line]=$array_a[$line]; 
} 

unset($array_a); //having this shows actually a bigger consume 
print_r($array_b); 

eco memory_get_usage();

Cuestiones relacionadas