Tengo algunas preguntas relacionadas con el uso de memoria en el siguiente ejemplo.
Si corro en el intérprete,
foo = ['bar' for _ in xrange(10000000)]
la memoria real utilizada en mi máquina aumenta
80.9mb
. Entonces yo,del foo
la memoria real se cae, pero solo a
30.4mb
. El intérprete utiliza la4.4mb
línea de base, ¿cuál es la ventaja de no liberar26mb
memoria al sistema operativo? ¿Es porque Python está "planeando con anticipación", pensando que puede volver a usar tanta memoria?¿Por qué se libera
50.5mb
en particular? ¿En qué cantidad se libera según?¿Hay alguna manera de obligar a Python a liberar toda la memoria que se usó (si sabe que no volverá a usar tanta memoria)?
NOTA
Esta pregunta es diferente de ¿Cómo puedo liberar memoria explícitamente en Python?
porque esta pregunta trata principalmente con el aumento del uso de memoria desde la línea de base incluso después de que el intérprete haya liberado objetos mediante la recolección de basura (con uso gc.collect
o no).
fuente
gc.collect
.Respuestas:
La memoria asignada en el montón puede estar sujeta a marcas altas. Esto se complica por las optimizaciones internas de Python para asignar objetos pequeños (
PyObject_Malloc
) en 4 grupos de KiB, clasificados para tamaños de asignación en múltiplos de 8 bytes, hasta 256 bytes (512 bytes en 3.3). Los grupos en sí están en arenas de 256 KiB, por lo que si solo se usa un bloque en un grupo, no se liberará todo el escenario de 256 KiB. En Python 3.3, el asignador de objetos pequeños se cambió a mapas de memoria anónimos en lugar del montón, por lo que debería funcionar mejor al liberar memoria.Además, los tipos integrados mantienen listas gratuitas de objetos asignados previamente que pueden o no usar el asignador de objetos pequeños. El
int
tipo mantiene una lista libre con su propia memoria asignada, y para borrarla es necesario llamarPyInt_ClearFreeList()
. Esto se puede llamar indirectamente haciendo un completogc.collect
.Pruébalo así y dime qué obtienes. Aquí está el enlace para psutil.Process.memory_info .
Salida:
Editar:
Cambié a medir en relación con el tamaño de VM del proceso para eliminar los efectos de otros procesos en el sistema.
El tiempo de ejecución C (por ejemplo, glibc, msvcrt) reduce el montón cuando el espacio libre contiguo en la parte superior alcanza un umbral constante, dinámico o configurable. Con glibc puede ajustar esto con
mallopt
(M_TRIM_THRESHOLD). Dado esto, no es sorprendente que el montón se reduzca más, incluso mucho más que el bloque que ustedfree
.En 3.x
range
no se crea una lista, por lo que la prueba anterior no creará 10 millones deint
objetos. Incluso si lo hiciera, elint
tipo en 3.x es básicamente un 2.xlong
, que no implementa una lista libre.fuente
memory_info()
lugar deget_memory_info()
yx
está definidoint
s incluso en Python 3, pero cada uno reemplaza al último en la variable de bucle para que no existan todos a la vez.Supongo que la pregunta que realmente te importa aquí es:
No no hay. Pero hay una solución fácil: procesos secundarios.
Si necesita 500 MB de almacenamiento temporal durante 5 minutos, pero después de eso debe ejecutar otras 2 horas y no volverá a tocar tanta memoria, genere un proceso secundario para realizar el trabajo intensivo en memoria. Cuando el proceso secundario desaparece, la memoria se libera.
Esto no es completamente trivial y gratuito, pero es bastante fácil y barato, lo que generalmente es lo suficientemente bueno como para que el intercambio valga la pena.
Primero, la forma más fácil de crear un proceso hijo es con
concurrent.futures
(o, para 3.1 yfutures
versiones anteriores, el backport en PyPI):Si necesita un poco más de control, use el
multiprocessing
módulo.Los costos son:
mmap
ped u otro; las API de memoria compartida enmultiprocessing
; etc.).struct
pueden ser o pueden ser idealesctypes
).fuente
eryksun ha respondido la pregunta n. ° 1, y he respondido la pregunta n. ° 3 (la original n. ° 4), pero ahora respondamos la pregunta n. ° 2:
En lo que se basa es, en última instancia, en toda una serie de coincidencias dentro de Python y
malloc
que son muy difíciles de predecir.Primero, dependiendo de cómo esté midiendo la memoria, es posible que solo esté midiendo páginas realmente asignadas a la memoria. En ese caso, cada vez que el localizador cambia una página, la memoria aparecerá como "liberada", aunque no se haya liberado.
O puede estar midiendo páginas en uso, que pueden o no contar páginas asignadas pero nunca tocadas (en sistemas que se sobreasignan de manera optimista, como Linux), páginas que están asignadas pero etiquetadas
MADV_FREE
, etc.Si realmente está midiendo las páginas asignadas (que en realidad no es algo muy útil, pero parece ser lo que está preguntando), y las páginas realmente se han desasignado, dos circunstancias en las que esto puede suceder: ha usado
brk
o equivalente para reducir el segmento de datos (muy raro hoy en día), o ha usadomunmap
o similar para liberar un segmento mapeado. (En teoría, también hay una variante menor para este último, ya que hay formas de liberar parte de un segmento mapeado, por ejemplo, robarloMAP_FIXED
para unMADV_FREE
segmento que inmediatamente desasigne).Pero la mayoría de los programas no asignan directamente cosas de páginas de memoria; usan un
malloc
asignador de estilo. Cuando llamafree
, el asignador solo puede liberar páginas al sistema operativo si resulta serfree
el último objeto vivo en una asignación (o en las últimas N páginas del segmento de datos). No hay forma de que su aplicación pueda predecir esto razonablemente, o incluso detectar que sucedió de antemano.CPython hace que esto sea aún más complicado: tiene un asignador de objetos de 2 niveles personalizado encima de un asignador de memoria personalizado encima
malloc
. (Consulte los comentarios de origen para obtener una explicación más detallada). Y además de eso, incluso a nivel de API C, mucho menos Python, ni siquiera controla directamente cuándo se desasignan los objetos de nivel superior.Entonces, cuando liberas un objeto, ¿cómo sabes si va a liberar memoria al sistema operativo? Bueno, primero debe saber que ha publicado la última referencia (incluidas las referencias internas que no conocía), lo que permite que el GC la desasigne. (A diferencia de otras implementaciones, al menos CPython desasignará un objeto tan pronto como se permita). Esto generalmente desasigna al menos dos cosas en el siguiente nivel hacia abajo (por ejemplo, para una cadena, está liberando el
PyString
objeto y el búfer de cadena )Si lo haces desasignar un objeto, para saber si esto hace que el siguiente nivel hacia abajo para cancelar la asignación de un bloque de almacenamiento de objetos, usted tiene que saber el estado interno del asignador de objeto, así como la forma en que está implementado. (Obviamente, esto no puede suceder a menos que desasigne la última cosa del bloque, e incluso así, puede que no suceda).
Si haces desasignar un bloque de almacenamiento de objetos, para saber si esto provoca una
free
llamada, usted tiene que saber el estado interno del asignador PyMem, así como la forma en que está implementado. (Nuevamente, debe desasignar el último bloque en uso dentro de unamalloc
región ed, e incluso entonces, puede que no suceda).Si hace
free
unamalloc
región ed, para saber si esto causa unmunmap
o equivalente (obrk
), debe conocer el estado interno delmalloc
, así como también cómo se implementa. Y este, a diferencia de los otros, es altamente específico de la plataforma. (Y, de nuevo, generalmente debe desasignar el último usomalloc
dentro de unmmap
segmento, e incluso entonces, puede que no suceda)Entonces, si quieres entender por qué se lanzó exactamente 50.5mb, tendrás que rastrearlo de abajo hacia arriba. ¿Por qué
malloc
anular el mapa de 50.5mb de páginas cuando realizó esas una o másfree
llamadas (probablemente por un poco más de 50.5mb)? Tendría que leer la plataformamalloc
y luego recorrer las diferentes tablas y listas para ver su estado actual. (En algunas plataformas, incluso puede hacer uso de información a nivel del sistema, que es prácticamente imposible de capturar sin hacer una instantánea del sistema para inspeccionar sin conexión, pero afortunadamente esto no suele ser un problema). Y luego tienes que haz lo mismo en los 3 niveles por encima de eso.Entonces, la única respuesta útil a la pregunta es "Porque".
A menos que esté haciendo un desarrollo de recursos limitados (por ejemplo, incrustado), no tiene motivos para preocuparse por estos detalles.
Y si está haciendo un desarrollo de recursos limitados, conocer estos detalles es inútil; prácticamente tiene que hacer una ejecución final alrededor de todos esos niveles y específicamente
mmap
la memoria que necesita a nivel de aplicación (posiblemente con un asignador de zona específico de aplicación simple y bien entendido en el medio).fuente
Primero, es posible que desee instalar miradas:
¡Entonces ejecútalo en la terminal!
En su código de Python, agregue al comienzo del archivo lo siguiente:
Después de usar la variable "Big" (por ejemplo: myBigVar) para la cual desea liberar memoria, escriba en su código de Python lo siguiente:
En otra terminal, ejecute su código de Python y observe en la terminal de "miradas", ¡cómo se gestiona la memoria en su sistema!
¡Buena suerte!
PD: supongo que estás trabajando en un sistema Debian o Ubuntu
fuente