2009-06-18 33 views
17

Mi aplicación PHP tiene un script de importación que puede importar registros.Cómo solucionar una fuga de memoria en PHP

Por el momento, que es la importación de un archivo CSV. Está leyendo cada línea del archivo CSV, una línea a la vez usando fgetcsv, y para cada línea está haciendo mucho de procesamiento en ese registro, incluidas las consultas de la base de datos, y luego pasa a la siguiente línea. No debería necesitar seguir acumulando más memoria.

Después de importar unos 2500 registros, PHP muere, diciendo que ha sobrepasado su límite de memoria (132 MB o menos).

El archivo CSV en sí mismo es solo un par de megas; el otro procesamiento que ocurre hace muchas comparaciones de cadenas, diffs, etc. Tengo una gran cantidad de código operando en él y sería difícil llegar a una 'muestra reproductora más pequeña'.

¿Cuáles son algunas buenas formas de encontrar y solucionar un problema de este tipo?

Causa del problema encontrado

que tienen una clase de depuración que registra todas mis consultas de bases de datos en tiempo de ejecución. Entonces esas cadenas de SQL, de unos 30KB de largo, permanecían en la memoria. Me doy cuenta de que esto no es adecuado para scripts diseñados para ejecutarse durante mucho tiempo.

Puede haber otras fuentes de pérdidas de memoria, pero estoy bastante seguro de que esta es la causa de mi problema.

+1

+1 Trabajo mucho con archivos CSV en PHP y con el tiempo me encontraré con este problema. – alex

+1

Tuve un problema similar en CodeIgniter al procesar un gran archivo de registro e insertarlo en un DB. Cambié las inserciones para usar CodeIgniters 'simple_query' en lugar del método usual' query' y reduje el uso de la memoria por un factor de 10. – MattSmith

Respuesta

5

Ayudaría echarle un vistazo al código, pero si quiere depurarlo usted mismo, eche un vistazo a Xdebug, le ayudará a perfilar su aplicación.

Por supuesto, dependiendo de lo que esté haciendo, es posible que esté acumulando algo de memoria, aunque parece que 132 MB ya son altos para 2500 registros. Por supuesto, puede tweak your memory limit en php.ini si es necesario.

¿Qué tamaño tiene el archivo CSV que está leyendo? ¿Y qué objetos y tipo de procesamiento le están haciendo?

+1

Gracias por la sugerencia de xdebug. He encontrado la causa ahora :) – thomasrutter

0

Es difícil decir la causa sin ver ningún código. Sin embargo, un problema típico son las referencias recursivas, es decir. el objeto A apunta al objeto B y al revés, lo que puede provocar que el GC se estropee.

No sé cómo está procesando actualmente el archivo, pero podría intentar leer el archivo solo una fila a la vez. Si lees todo el archivo a la vez, puede consumir más memoria.

Esta es en realidad una de las razones por las que a menudo prefiero Python para tareas de procesamiento por lotes.

+0

Está leyendo CSV una línea a la vez. El CSV completo es solo un par de megas; es otro procesamiento que parece ser el problema. – thomasrutter

+1

¿No se les llama referencias circulares? – stefs

+0

@Schnalle absolutamente correcto. Tenía demasiado sueño para recordar el término correcto =) –

2

Depende de cómo está borrando las variables después de haber terminado con ellas.

Parece que ha terminado con el registro pero aún está almacenando la información en alguna parte. Use unset() para borrar las variables si tiene dudas.

Proporcione una muestra de código de reproducción mínima para ver dónde está funcionando toda esa memoria si esto no ayuda.

BTW, producir la muestra de código más pequeña que reproduzca el problema es una gran técnica de depuración porque obliga a pasar por el código nuevamente, con cuidado.

1

¿Cómo está leyendo el archivo? Si utiliza los contenidos de fread/fileget u otras funciones similares, consumirá todo el tamaño del archivo (o lo que cargue con fread) en la memoria, ya que todo el archivo se carga en el momento de la llamada. Sin embargo, si usa fgetcsv si solo lee una línea a la vez según la longitud de la línea, esto puede ser dramáticamente más fácil en su memoria.

También asegúrese de estar reutilizando tantas variables como sea posible en cada ciclo. Verifique que no haya una matriz con grandes cantidades de datos en ellos.

Como última nota también asegurarse de que está abriendo el archivo antes de su bucle y luego cerrándola epílogos:

$fh = fopen(...); 
while(true) 
{ 
//... 
} 
fclose($fh); 

usted no lo hace realmente quiero hacer esto:

while(true) 
{ 
$fh = fopen(...); 
//... 
fclose($fh); 
} 

Y, como otros, han dicho que será difícil de decir sin ver un código.

+0

Estaba usando fgetcsv, lo siento, olvidé mencionar. Estaba bastante seguro de que el problema no estaba en leer el archivo, porque el archivo en sí es relativamente pequeño. – thomasrutter

0

¿Puede cambiar su memory_limit en su php.ini?

Además, ¿podría hacer unset ($ var) en variables liberar algo de memoria? ¿Podría $ var = null ayudar también?

Ver también esta pregunta: What's better at freeing memory with PHP: unset() or $var = null

+0

Buena sugerencia, pero el límite de memoria ya está en 128MB más o menos, y aumentarlo solo compraría la posibilidad de que funcione un poco más. Eventualmente me gustaría poder hacer importaciones de más de 10, quizás 50 veces este tamaño. – thomasrutter

+0

Sí, me doy cuenta de que no es la solución ideal. Solo pensé en mencionarlo. – alex

7

Si lo hace, de hecho, sospechamos que hay solo uno o dos pérdidas de memoria en la secuencia de comandos que están provocando que se bloquee, a continuación, usted debe tomar los pasos siguientes:

  • Cambio memory_limit a algo pequeño, como 500KB
  • comentario a cabo todas menos una de las etapas de tratamiento que se aplica a cada fila.
  • Ejecute el procesamiento limitado en todo el archivo CSV y vea si se puede completar.
  • Poco a poco agregue más pasos hacia atrás y observe si el uso de la memoria aumenta.

Ejemplo:

ini_set('memory_limit', 1024 * 500); 
$fp = fopen("test.csv", 'r'); 
while($row = fgetcsv($fp)) { 
    validate_row($row);   // step 1: validate 
    // add these back in one by one and keep an eye on memory usage 
    //calculate_fizz($row);  // step 2: fizz 
    //calculate_buzz($row);  // step 3: buzz 
    //triangulate($row);  // step 4: triangulate 
} 
echo "Memory used: ", memory_get_peak_usage(), "\n"; 

El peor escenario es que todo de sus pasos de procesamiento son moderadamente ineficiente y que se necesitan para optimizar todos ellos.

+1

Gracias por la sugerencia. Es una buena sugerencia para cualquier persona con un problema similar, pero descubrí que la función de "rastreo" de xdebug es muy útil para encontrar la causa aquí. – thomasrutter

2

puede intentar una instalación local de php5.3 y llamar a http://www.php.net/manual/en/function.gc-collect-cycles.php.

gc_collect_cycles - colección Fuerzas de cualquier ciclo de basura existentes

si la situación mejora, que al menos se verifica (en la de) el problema (s).

0

Estaba teniendo el mismo problema, y ​​también se debió a la creación de perfiles de la base de datos (Zend_Db_Profiler_Firebug). En mi caso, estaba perdiendo 1mb por minuto.Se suponía que este script se ejecutaba durante días, por lo que se bloqueaba en unas pocas horas.