[Madrid-pm] Memory leaks

Joaquin Ferrero explorer en joaquinferrero.com
Mie Abr 11 15:51:41 PDT 2007


El mié, 11-04-2007 a las 23:10 +0200, Bruno escribió:
> Hola!
> 
> He creado alguna estructura de módulos para hacer PDFs con las fotos.
> Uso Rose::DB para contactar la BdD, Imager para procesar las fotos y
> PDF::API2 para producir PDFs. Todo funciona muy bien...
> 
> ...pero usa demasiado memoría y con el documento de mas que 9 páginas
> se rompe con mensaje de este tipo:
> 
> Out of memory during "large" request for 8392704 bytes, total sbrk()
> is 411217920 bytes at
> /usr/local/lib/perl5/site_perl/5.8.8/Rose/DB/Object.pm line 384.
> 
> Uso undef para todos los lexicales, que pueden ocupar mucha memoría.
> Devel::Cycle no mostró ningunos ciclos en las estructuras de Rose::DB.
> Con Devel::LeakTrace todo funciona tan lento, que no se puede ver
> ningun progreso. Devel::Leak... pues, me da información sobre SVs que
> existen, pero no me dice - donde.
> 
> ¿Alguna sugerencia?
> 

Qué curioso... justo ese problema lo hemos tenido aquí en mi trabajo.

Uno de los desarrolladores hizo un programa que tenía que generar 40.000
documentos pdf, usando también el conector con OpenDocument de
OpenOffice (yo no estoy en ese tema, así que cuento lo que me han
dicho).

El caso es que pasaba lo mismo: pasado un tiempo, la memoria del sistema
se agotaba. Me pidieron consejo y les dije lo mismo que Bruno: variables
léxicas, usar undef en algunos casos, etc. Pero nada, aquello se
disparaba. Les aconsejé ver el tamaño de las variables con el módulo
Devel::Size, pero me dijeron que ya encontraron una solución mejor: cada
vez que el bucle interno del programa quería generar un documento,
ejecutaba un script Perl nuevo que se encargaba de eso. Así que cuando
el script terminaba, pues era claro que se liberaba la memoria.

Me suena haber leído un artículo sobre cómo hacer profiling de la
memoria consumida, pero no soy capaz de encontrarlo. Todo lo que
encuentro es profiling de tiempos y alguna referencia a un módulo
llamado Devel::Memory, pero tampoco se encuentra en CPAN. Recuerdo que
el artículo dibujaba una gráfica con el espacio ocupado al entrar en una
subrutina, pero nada, que no sé dónde lo leí.

Y es el caso que Bruno es la tercera persona que me pregunta por temas
de memoria de Perl en sólo esta semana...

En mi caso tenía que leer un documento de 218Mb y pasarla a una
estructura de hashes de hashes de arrays de hashes de hashes de hashes
(puf!) y luego volcar todo eso en generar ficheros Excel. Bueno, pues
sólo la primera parte de leer la estructura de datos Perl se comía 880Mb
de memoria RAM. El resto se lleva otros 200Mb más.

Mi Windows en el trabajo tiene 1Gb de RAM y 1Gb de swap. En teoría se
podría reservar memoria hasta el 1.5Gb aprox. (dejando un poco para el
sistema y los procesos que corren permanentemente). PERO windows informa
a los programas que se dispone de 2Gb de recursos.

El caso es que con 1.4Gb ocupados, el sistema no puede más y se dedica
más tiempo (mucho más) a trasegar información entre la RAM y el disco.
PERO sigue informando que dispone de 0.5Gb más de recursos (visto esto
en el Administrador de Tareas), por lo que los procesos continúan como
si no pasara nada.

Desde luego yo estoy acostumbrado a hacer estas burradas y mucho más
grandes en máquinas con más memoria (2Gb, 3Gb y 4Gb), pero con un
sistema Linux sé que el sistema me puede ofrecer lo que realmente tiene.
Y si no tiene, el programa cae.

En cuanto a Bruno, decirle que está siguiendo los caminos básicos de la
liberación de memoria:

1. undef
2. variable fuera de ámbito
3. comprobar que su contador de referencias es 0 (Devel::Cycle,
Devel::Leak)

Atención, también depende de la versión Perl que se esté ejecutando.
Con un Perl viejo, el siguiente programa funcionará como indican los
comentarios:

#!/usr/bin/perl

use warnings;
use strict;
$| = 1;
{ #start of scope
    my $string;
    for ( 1 .. 100000 ) {
        $string .= ( 'x' x 1000 );
    }
    print "press enter to release";
    <>;
    undef $string; # note that memory does not get released

    print "undefined but still in scope of sub, hit enter\n";
    <>;

    # if the variable only goes out of scope.
    # you *need* to undef it!
} #end of scope

print "ok,out of scope, press enter to exit";
<>;

__END__

Vamos, que dice que hay que hacer un 'undef' a la variable antes de que
ésta salga de su ámbito.

Pero con mi perl 5.8.8, no es así. En cuanto se ejecutó 'undef' la
memoria quedó liberada.
Más bien lo contrario.
Si NO hago 'undef', la memoria NO queda liberada hasta que termina el
programa.

Leer la sección Class Data en perltoot: la memoria ocupada por las
variables léxicas NO es liberada al salir de ámbito por si es
referenciada por alguna función declarada en el mismo ámbito en que se
declaró esa variable. Esto es la base de las Closures:
http://perldoc.perl.org/perlfaq7.html#What's-a-closure%3f

(Sacado de perlmongers)
Entendiendo también cómo reservan y liberan memoria los sistemas
actuales podemos aprender a la hora de hacer las peticiones de ese
recurso.

Por ejemplo, en los sistemas operativos que usamos hoy en día (Win32,
Mac OS, VMS y algunas versiones de la glibc), se 'garantiza' que la
memoria que el programa deja de usar se devuelve al sistema. Pero esto
no siempre es así. Ese espacio de memoria debe ser de un tamaño
determinado. En glibc es alrededor de 1Mb. Si es más pequeño, el sistema
no se preocupa de hacerlo hasta que algún demonio se encarga de esa
tarea (todo para ahorrar el tiempo que ocupa esa tarea).

Ejemplo: si reservamos 1 bloque de 100Mb y lo liberamos, seguro que
vuelve al sistema. Pero si reservamos 100Mb de bloques de 100 bytes,
seguro que no.
(fin de la nota de perlmongers)

Esto me recuerda que en Perl se puede reservar memoria en los arrays con
la asignación

	$#array = 10000;

para indicar, por ejemplo, que vamos a usar un array de ese número de
elementos (bueno, en realidad uno más).

Para hashes tenemos algo parecido:

	keys(%hash) = 10000;

aunque eso no significa que se reserve espacio para 10.000 claves. Ver
perldata, sección Scalar values.

(perdón por el rollo)

-- 
JoaquinFerrero.com                 Linux User #109802
msn/jab explorer en jab.pucela.net    GPG/PGP 0x42DDB1FE
skype joaquinferrero               phone +34670654075



Más información sobre la lista de distribución Madrid-pm