php - Diagnóstico de fugas de memoria: tamaño de memoria permitido de#bytes agotados
memory-leaks (13)
Aquí hay un truco que hemos usado para identificar qué scripts están usando la mayor cantidad de memoria en nuestro servidor.
Guarde el siguiente fragmento en un archivo en, por ejemplo, /usr/local/lib/php/strangecode_log_memory_usage.inc.php
:
<?php
function strangecode_log_memory_usage()
{
$site = '''' == getenv(''SERVER_NAME'') ? getenv(''SCRIPT_FILENAME'') : getenv(''SERVER_NAME'');
$url = $_SERVER[''PHP_SELF''];
$current = memory_get_usage();
$peak = memory_get_peak_usage();
error_log("$site current: $current peak: $peak $url/n", 3, ''/var/log/httpd/php_memory_log'');
}
register_shutdown_function(''strangecode_log_memory_usage'');
Emplearlo agregando lo siguiente a httpd.conf:
php_admin_value auto_prepend_file /usr/local/lib/php/strangecode_log_memory_usage.inc.php
Luego analiza el archivo de registro en /var/log/httpd/php_memory_log
Es posible que necesite touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
antes de que su usuario pueda escribir en el archivo de registro.
Me he encontrado con el mensaje de error temido, posiblemente a través de esfuerzo minucioso, PHP se ha quedado sin memoria:
Tamaño de memoria permitido de #### bytes agotados (intentado asignar #### bytes) en file.php en la línea 123
Aumentando el límite
Si sabe lo que está haciendo y quiere aumentar el límite, vea memory_limit :
ini_set(''memory_limit'', ''16M'');
ini_set(''memory_limit'', -1); // no limit
¡Tener cuidado! ¡Solo puedes resolver el síntoma y no el problema!
Diagnosticando la fuga:
El mensaje de error apunta a una línea dentro de un bucle que creo que está goteando, o acumulando innecesariamente, memoria. memory_get_usage()
instrucciones memory_get_usage()
al final de cada iteración y puedo ver que el número crece lentamente hasta que alcanza el límite:
foreach ($users as $user) {
$task = new Task;
$task->run($user);
unset($task); // Free the variable in an attempt to recover memory
print memory_get_usage(true); // increases over time
}
A los efectos de esta pregunta, supongamos que el peor código de espagueti imaginable se esconde en el alcance global en algún lugar en $user
o Task
.
¿Qué herramientas, trucos de PHP o depuración de vudú me pueden ayudar a encontrar y solucionar el problema?
Encontré el mismo problema, y mi solución fue reemplazar foreach con un regular para. No estoy seguro acerca de los detalles, pero parece que foreach crea una copia (o de alguna manera una nueva referencia) para el objeto. Usando un ciclo for para regular, accede al elemento directamente.
Hay varios puntos posibles de pérdida de memoria en php:
- php en sí
- extensión php
- la biblioteca php que usas
- tu código php
Es bastante difícil encontrar y arreglar los primeros 3 sin una profunda ingeniería inversa ni conocimiento del código fuente php. Para el último, puede usar la búsqueda binaria de código de fuga de memoria con memory_get_usage
Le sugiero que consulte el manual de PHP o agregue la función gc_enable()
para recolectar la basura ... Es decir, las pérdidas de memoria no afectan la forma en que se ejecuta el código.
PD: php tiene un recolector de basura gc_enable()
que no toma argumentos.
Llego un poco tarde a esta conversación, pero compartiré algo relacionado con Zend Framework.
Tuve un problema de pérdida de memoria después de instalar php 5.3.8 (usando phpfarm) para trabajar con una aplicación ZF desarrollada con php 5.2.9. Descubrí que la pérdida de memoria se estaba desencadenando en el archivo httpd.conf de Apache, en mi definición de host virtual, donde dice SetEnv APPLICATION_ENV "development"
. Después de comentar esta línea, las pérdidas de memoria se detuvieron. Estoy tratando de encontrar una solución en línea en mi script php (principalmente definiéndola manualmente en el archivo principal index.php).
Me di cuenta una vez en un script antiguo que PHP mantendría la variable "como" en el ámbito incluso después de mi ciclo foreach. Por ejemplo,
foreach($users as $user){
$user->doSomething();
}
var_dump($user); // would output the data from the last $user
No estoy seguro de si las futuras versiones de PHP arreglaron esto o no desde que lo vi. Si este es el caso, puede unset($user)
después de la línea doSomething()
para borrarlo de la memoria. YMMV.
No lo mencioné explícitamente, pero xdebug hace un excelente trabajo al perfilar el tiempo y la memoria (a partir de 2.6 ). Puede tomar la información que genera y pasarla a una interfaz gráfica de usuario de su elección: webgrind (solo tiempo), kcachegrind , qcachegrind u otros y genera árboles y gráficos de llamadas muy útiles para que pueda encontrar las fuentes de sus diversos problemas. .
No vi que se mencionara aquí, pero una cosa que podría ser útil es usar xdebug y xdebug_debug_zval (''variableName'') para ver el refcount.
También puedo proporcionar un ejemplo de una extensión de php en el camino: Zend Server''s Z-Ray. Si la recopilación de datos está habilitada, el uso de la memoria aumentará en cada iteración como si la recolección de basura estuviera desactivada.
PHP no tiene un recolector de basura. Utiliza el recuento de referencias para administrar la memoria. Por lo tanto, la fuente más común de pérdidas de memoria son referencias cíclicas y variables globales. Si usas un framework, tendrás mucho código para buscarlo, me temo. El instrumento más simple es colocar selectivamente las llamadas a memory_get_usage
y memory_get_usage
a donde el código se filtra. También puede usar xdebug para crear un rastro del código. Ejecute el código con los rastreos de ejecución y show_mem_delta
.
Recientemente encontré este problema en una aplicación, bajo circunstancias que me parecen similares. Una secuencia de comandos que se ejecuta en cli de PHP que recorre muchas iteraciones. Mi script depende de varias bibliotecas subyacentes. Sospecho que una biblioteca en particular es la causa y pasé varias horas en vano tratando de agregar métodos de destrucción apropiados a sus clases en vano. Enfrentado a un largo proceso de conversión a una biblioteca diferente (que podría tener los mismos problemas), se me ocurrió una solución aproximada para el problema en mi caso.
En mi situación, en un Linux cli, estaba revisando un montón de registros de usuario y para cada uno de ellos creando una nueva instancia de varias clases que creé. Decidí intentar crear las nuevas instancias de las clases utilizando el método de ejecución de PHP para que esos procesos se ejecutaran en un "nuevo hilo". Aquí hay una muestra realmente básica de a lo que me refiero:
foreach ($ids as $id) {
$lines=array();
exec("php ./path/to/my/classes.php $id", $lines);
foreach ($lines as $line) { echo $line."/n"; } //display some output
}
Obviamente, este enfoque tiene limitaciones, y uno debe ser consciente de los peligros de esto, ya que sería fácil crear un trabajo de conejo, sin embargo, en algunos casos raros podría ayudar a superar un punto difícil, hasta que se encuentre una solución mejor. como en mi caso
Recientemente noté que las funciones PHP 5.3 de PHP dejan memoria adicional utilizada cuando se eliminan.
for ($i = 0; $i < 1000; $i++)
{
//$log = new Log;
$log = function() { return new Log; };
//unset($log);
}
No estoy seguro de por qué, pero parece tomar un extra de 250 bytes cada lambda, incluso después de que se elimine la función.
Si lo que dice acerca de que PHP solo hace GC después de que una función es verdadera, podría envolver el contenido del ciclo dentro de una función como una solución / experimento.
Un gran problema que tuve fue usar create_function . Al igual que en las funciones lambda, deja el nombre temporal generado en la memoria.
Otra causa de pérdidas de memoria (en el caso de Zend Framework) es Zend_Db_Profiler. Asegúrese de que esté deshabilitado si ejecuta scripts en Zend Framework. Por ejemplo, tuve en mi application.ini lo siguiente:
resources.db.profiler.enabled = true
resources.db.profiler.class = Zend_Db_Profiler_Firebug
Ejecutando aproximadamente 25,000 consultas + cargas de procesamiento antes de eso, trajo la memoria a un bonito 128Mb (límite de memoria máximo).
Simplemente estableciendo:
resources.db.profiler.enabled = false
fue suficiente para mantenerlo por debajo de 20 Mb
Y este script se estaba ejecutando en CLI, pero estaba creando instancias de Zend_Application y ejecutando Bootstrap, por lo que usó la configuración de "desarrollo".
Realmente ayudó a ejecutar el script con perfiles xDebug