¿Fuga de memoria al usar cadenas <128KB en Python?

Título original: Fuga de memoria para abrir archivos <128KB en Python?

Pregunta original

Veo lo que creo que es una pérdida de memoria al ejecutar mi script de Python. Aquí está mi guión:

import sys import time class MyObj(object): def __init__(self, filename): with open(filename) as f: self.att = f.read() def myfunc(filename): mylist = [MyObj(filename) for x in xrange(100)] len(mylist) return [] def main(): filename = sys.argv[1] myfunc(filename) time.sleep(3600) if __name__ == '__main__': main() 

La función principal llama a myfunc() que crea una lista de 100 objetos que cada uno abre y lee un archivo. Después de regresar de myfunc() , esperaría que la memoria de la lista de 100 elementos y de la lectura del archivo se liberara ya que ya no se hace referencia a ellos. Sin embargo, cuando verifico el uso de la memoria con el comando ps , el proceso de Python usa aproximadamente 10,000 KB más de memoria que un proceso de Python ejecutado desde una secuencia de comandos con las líneas 12 y 13 comentadas.

Lo extraño es que la pérdida de memoria (si es lo que es) solo parece ocurrir para archivos de menos de 128 KB de tamaño. Creé una secuencia de comandos bash para ejecutar esta secuencia de comandos con archivos que varían en tamaño desde 1 KB hasta 200 KB y el aumento de memoria se detuvo cuando el tamaño de los archivos llegó a 128 KB. Aquí está el guión de bash:

 #!/bin/bash echo "PID RSS S TTY TIME COMMAND" > output.txt for i in `seq 1 200`; do python debug_memory.py "data/stuff_${i}K.txt" & pid=$! sleep 0.1 ps -e -O rss | grep $pid | grep -v grep >> output.txt kill $pid done 

Aquí está la salida del script bash:

 PID RSS S TTY TIME COMMAND 28471 5552 S pts/16 00:00:00 python debug_memory.py data/stuff_1K.txt 28477 5656 S pts/16 00:00:00 python debug_memory.py data/stuff_2K.txt 28483 5756 S pts/16 00:00:00 python debug_memory.py data/stuff_3K.txt 28488 5852 S pts/16 00:00:00 python debug_memory.py data/stuff_4K.txt 28494 5952 S pts/16 00:00:00 python debug_memory.py data/stuff_5K.txt 28499 6052 S pts/16 00:00:00 python debug_memory.py data/stuff_6K.txt 28505 6156 S pts/16 00:00:00 python debug_memory.py data/stuff_7K.txt 28511 6256 S pts/16 00:00:00 python debug_memory.py data/stuff_8K.txt 28516 6356 S pts/16 00:00:00 python debug_memory.py data/stuff_9K.txt 28522 6452 S pts/16 00:00:00 python debug_memory.py data/stuff_10K.txt 28527 6552 S pts/16 00:00:00 python debug_memory.py data/stuff_11K.txt 28533 6656 S pts/16 00:00:00 python debug_memory.py data/stuff_12K.txt 28539 6756 S pts/16 00:00:00 python debug_memory.py data/stuff_13K.txt 28544 6852 S pts/16 00:00:00 python debug_memory.py data/stuff_14K.txt 28550 6952 S pts/16 00:00:00 python debug_memory.py data/stuff_15K.txt 28555 7056 S pts/16 00:00:00 python debug_memory.py data/stuff_16K.txt 28561 7156 S pts/16 00:00:00 python debug_memory.py data/stuff_17K.txt 28567 7252 S pts/16 00:00:00 python debug_memory.py data/stuff_18K.txt 28572 7356 S pts/16 00:00:00 python debug_memory.py data/stuff_19K.txt 28578 7452 S pts/16 00:00:00 python debug_memory.py data/stuff_20K.txt 28584 7556 S pts/16 00:00:00 python debug_memory.py data/stuff_21K.txt 28589 7652 S pts/16 00:00:00 python debug_memory.py data/stuff_22K.txt 28595 7756 S pts/16 00:00:00 python debug_memory.py data/stuff_23K.txt 28600 7852 S pts/16 00:00:00 python debug_memory.py data/stuff_24K.txt 28606 7952 S pts/16 00:00:00 python debug_memory.py data/stuff_25K.txt 28612 8052 S pts/16 00:00:00 python debug_memory.py data/stuff_26K.txt 28617 8152 S pts/16 00:00:00 python debug_memory.py data/stuff_27K.txt 28623 8252 S pts/16 00:00:00 python debug_memory.py data/stuff_28K.txt 28629 8356 S pts/16 00:00:00 python debug_memory.py data/stuff_29K.txt 28634 8452 S pts/16 00:00:00 python debug_memory.py data/stuff_30K.txt 28640 8556 S pts/16 00:00:00 python debug_memory.py data/stuff_31K.txt 28645 8656 S pts/16 00:00:00 python debug_memory.py data/stuff_32K.txt 28651 8756 S pts/16 00:00:00 python debug_memory.py data/stuff_33K.txt 28657 8856 S pts/16 00:00:00 python debug_memory.py data/stuff_34K.txt 28662 8956 S pts/16 00:00:00 python debug_memory.py data/stuff_35K.txt 28668 9056 S pts/16 00:00:00 python debug_memory.py data/stuff_36K.txt 28674 9156 S pts/16 00:00:00 python debug_memory.py data/stuff_37K.txt 28679 9256 S pts/16 00:00:00 python debug_memory.py data/stuff_38K.txt 28685 9352 S pts/16 00:00:00 python debug_memory.py data/stuff_39K.txt 28691 9452 S pts/16 00:00:00 python debug_memory.py data/stuff_40K.txt 28696 9552 S pts/16 00:00:00 python debug_memory.py data/stuff_41K.txt 28702 9656 S pts/16 00:00:00 python debug_memory.py data/stuff_42K.txt 28707 9756 S pts/16 00:00:00 python debug_memory.py data/stuff_43K.txt 28713 9852 S pts/16 00:00:00 python debug_memory.py data/stuff_44K.txt 28719 9952 S pts/16 00:00:00 python debug_memory.py data/stuff_45K.txt 28724 10052 S pts/16 00:00:00 python debug_memory.py data/stuff_46K.txt 28730 10156 S pts/16 00:00:00 python debug_memory.py data/stuff_47K.txt 28739 10256 S pts/16 00:00:00 python debug_memory.py data/stuff_48K.txt 28746 10352 S pts/16 00:00:00 python debug_memory.py data/stuff_49K.txt 28752 10452 S pts/16 00:00:00 python debug_memory.py data/stuff_50K.txt 28757 10556 S pts/16 00:00:00 python debug_memory.py data/stuff_51K.txt 28763 10656 S pts/16 00:00:00 python debug_memory.py data/stuff_52K.txt 28769 10752 S pts/16 00:00:00 python debug_memory.py data/stuff_53K.txt 28774 10852 S pts/16 00:00:00 python debug_memory.py data/stuff_54K.txt 28780 10952 S pts/16 00:00:00 python debug_memory.py data/stuff_55K.txt 28786 11052 S pts/16 00:00:00 python debug_memory.py data/stuff_56K.txt 28791 11152 S pts/16 00:00:00 python debug_memory.py data/stuff_57K.txt 28797 11256 S pts/16 00:00:00 python debug_memory.py data/stuff_58K.txt 28802 11356 S pts/16 00:00:00 python debug_memory.py data/stuff_59K.txt 28808 11452 S pts/16 00:00:00 python debug_memory.py data/stuff_60K.txt 28814 11556 S pts/16 00:00:00 python debug_memory.py data/stuff_61K.txt 28819 11656 S pts/16 00:00:00 python debug_memory.py data/stuff_62K.txt 28825 11752 S pts/16 00:00:00 python debug_memory.py data/stuff_63K.txt 28831 11852 S pts/16 00:00:00 python debug_memory.py data/stuff_64K.txt 28836 11956 S pts/16 00:00:00 python debug_memory.py data/stuff_65K.txt 28842 12052 S pts/16 00:00:00 python debug_memory.py data/stuff_66K.txt 28847 12152 S pts/16 00:00:00 python debug_memory.py data/stuff_67K.txt 28853 12256 S pts/16 00:00:00 python debug_memory.py data/stuff_68K.txt 28859 12356 S pts/16 00:00:00 python debug_memory.py data/stuff_69K.txt 28864 12452 S pts/16 00:00:00 python debug_memory.py data/stuff_70K.txt 28871 12556 S pts/16 00:00:00 python debug_memory.py data/stuff_71K.txt 28877 12652 S pts/16 00:00:00 python debug_memory.py data/stuff_72K.txt 28883 12756 S pts/16 00:00:00 python debug_memory.py data/stuff_73K.txt 28889 12856 S pts/16 00:00:00 python debug_memory.py data/stuff_74K.txt 28894 12952 S pts/16 00:00:00 python debug_memory.py data/stuff_75K.txt 28900 13056 S pts/16 00:00:00 python debug_memory.py data/stuff_76K.txt 28906 13156 S pts/16 00:00:00 python debug_memory.py data/stuff_77K.txt 28911 13256 S pts/16 00:00:00 python debug_memory.py data/stuff_78K.txt 28917 13352 S pts/16 00:00:00 python debug_memory.py data/stuff_79K.txt 28922 13452 S pts/16 00:00:00 python debug_memory.py data/stuff_80K.txt 28928 13556 S pts/16 00:00:00 python debug_memory.py data/stuff_81K.txt 28934 13652 S pts/16 00:00:00 python debug_memory.py data/stuff_82K.txt 28939 13752 S pts/16 00:00:00 python debug_memory.py data/stuff_83K.txt 28945 13852 S pts/16 00:00:00 python debug_memory.py data/stuff_84K.txt 28951 13952 S pts/16 00:00:00 python debug_memory.py data/stuff_85K.txt 28956 14052 S pts/16 00:00:00 python debug_memory.py data/stuff_86K.txt 28962 14152 S pts/16 00:00:00 python debug_memory.py data/stuff_87K.txt 28967 14256 S pts/16 00:00:00 python debug_memory.py data/stuff_88K.txt 28973 14352 S pts/16 00:00:00 python debug_memory.py data/stuff_89K.txt 28979 14456 S pts/16 00:00:00 python debug_memory.py data/stuff_90K.txt 28984 14552 S pts/16 00:00:00 python debug_memory.py data/stuff_91K.txt 28990 14652 S pts/16 00:00:00 python debug_memory.py data/stuff_92K.txt 28996 14756 S pts/16 00:00:00 python debug_memory.py data/stuff_93K.txt 29001 14852 S pts/16 00:00:00 python debug_memory.py data/stuff_94K.txt 29007 14956 S pts/16 00:00:00 python debug_memory.py data/stuff_95K.txt 29012 15052 S pts/16 00:00:00 python debug_memory.py data/stuff_96K.txt 29018 15156 S pts/16 00:00:00 python debug_memory.py data/stuff_97K.txt 29024 15252 S pts/16 00:00:00 python debug_memory.py data/stuff_98K.txt 29029 15360 S pts/16 00:00:00 python debug_memory.py data/stuff_99K.txt 29035 15456 S pts/16 00:00:00 python debug_memory.py data/stuff_100K.txt 29040 15556 S pts/16 00:00:00 python debug_memory.py data/stuff_101K.txt 29046 15652 S pts/16 00:00:00 python debug_memory.py data/stuff_102K.txt 29052 15756 S pts/16 00:00:00 python debug_memory.py data/stuff_103K.txt 29057 15852 S pts/16 00:00:00 python debug_memory.py data/stuff_104K.txt 29063 15952 S pts/16 00:00:00 python debug_memory.py data/stuff_105K.txt 29069 16056 S pts/16 00:00:00 python debug_memory.py data/stuff_106K.txt 29074 16152 S pts/16 00:00:00 python debug_memory.py data/stuff_107K.txt 29080 16256 S pts/16 00:00:00 python debug_memory.py data/stuff_108K.txt 29085 16356 S pts/16 00:00:00 python debug_memory.py data/stuff_109K.txt 29091 16452 S pts/16 00:00:00 python debug_memory.py data/stuff_110K.txt 29097 16552 S pts/16 00:00:00 python debug_memory.py data/stuff_111K.txt 29102 16652 S pts/16 00:00:00 python debug_memory.py data/stuff_112K.txt 29108 16756 S pts/16 00:00:00 python debug_memory.py data/stuff_113K.txt 29113 16852 S pts/16 00:00:00 python debug_memory.py data/stuff_114K.txt 29119 16952 S pts/16 00:00:00 python debug_memory.py data/stuff_115K.txt 29125 17056 S pts/16 00:00:00 python debug_memory.py data/stuff_116K.txt 29130 17156 S pts/16 00:00:00 python debug_memory.py data/stuff_117K.txt 29136 17256 S pts/16 00:00:00 python debug_memory.py data/stuff_118K.txt 29141 17356 S pts/16 00:00:00 python debug_memory.py data/stuff_119K.txt 29147 17452 S pts/16 00:00:00 python debug_memory.py data/stuff_120K.txt 29153 17556 S pts/16 00:00:00 python debug_memory.py data/stuff_121K.txt 29158 17656 S pts/16 00:00:00 python debug_memory.py data/stuff_122K.txt 29164 17756 S pts/16 00:00:00 python debug_memory.py data/stuff_123K.txt 29170 17856 S pts/16 00:00:00 python debug_memory.py data/stuff_124K.txt 29175 17952 S pts/16 00:00:00 python debug_memory.py data/stuff_125K.txt 29181 18056 S pts/16 00:00:00 python debug_memory.py data/stuff_126K.txt 29186 18152 S pts/16 00:00:00 python debug_memory.py data/stuff_127K.txt 29192 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_128K.txt 29198 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_129K.txt 29203 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_130K.txt 29209 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_131K.txt 29215 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_132K.txt 29220 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_133K.txt 29226 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_134K.txt 29231 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_135K.txt 29237 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_136K.txt 29243 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_137K.txt 29248 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_138K.txt 29254 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_139K.txt 29260 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_140K.txt 29265 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_141K.txt 29271 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_142K.txt 29276 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_143K.txt 29282 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_144K.txt 29288 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_145K.txt 29293 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_146K.txt 29299 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_147K.txt 29305 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_148K.txt 29310 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_149K.txt 29316 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_150K.txt 29321 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_151K.txt 29327 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_152K.txt 29333 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_153K.txt 29338 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_154K.txt 29344 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_155K.txt 29349 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_156K.txt 29355 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_157K.txt 29361 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_158K.txt 29366 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_159K.txt 29372 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_160K.txt 29378 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_161K.txt 29383 5460 S pts/16 00:00:00 python debug_memory.py data/stuff_162K.txt 29389 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_163K.txt 29394 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_164K.txt 29400 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_165K.txt 29406 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_166K.txt 29411 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_167K.txt 29417 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_168K.txt 29423 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_169K.txt 29428 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_170K.txt 29434 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_171K.txt 29439 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_172K.txt 29445 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_173K.txt 29451 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_174K.txt 29456 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_175K.txt 29463 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_176K.txt 29483 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_177K.txt 29489 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_178K.txt 29496 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_179K.txt 29501 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_180K.txt 29507 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_181K.txt 29512 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_182K.txt 29518 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_183K.txt 29524 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_184K.txt 29529 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_185K.txt 29535 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_186K.txt 29541 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_187K.txt 29546 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_188K.txt 29552 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_189K.txt 29557 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_190K.txt 29563 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_191K.txt 29569 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_192K.txt 29574 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_193K.txt 29580 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_194K.txt 29586 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_195K.txt 29591 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_196K.txt 29597 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_197K.txt 29602 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_198K.txt 29608 5456 S pts/16 00:00:00 python debug_memory.py data/stuff_199K.txt 29614 5452 S pts/16 00:00:00 python debug_memory.py data/stuff_200K.txt 

¿Alguien puede explicar lo que está pasando? ¿Por qué veo un aumento en el uso de memoria cuando uso archivos <128KB?

Mi entorno de prueba completo se encuentra aquí: https://github.com/saltycrane/debugging-python-memory-usage/tree/50f73358c7a84a504333ce9c4071b0f3537bbc0f

Estoy ejecutando Python 2.7.3 en Ubuntu 12.04.

ACTUALIZACIÓN 1

Este problema no es específico para trabajar con archivos <128K de tamaño. Obtengo los mismos resultados al establecer el atributo de objeto en un valor del mismo tamaño que se leyó en el archivo. Aquí está el código actualizado:

 import sys import time class MyObj(object): def __init__(self, size_kb): self.att = ' ' * int(size_kb) * 1024 def myfunc(size_kb): mylist = [MyObj(size_kb) for x in xrange(100)] len(mylist) return [] def main(): size_kb = sys.argv[1] myfunc(size_kb) time.sleep(3600) if __name__ == '__main__': main() 

Ejecutar este script da resultados similares. El entorno de prueba actualizado se encuentra aquí: https://github.com/saltycrane/debugging-python-memory-usage/tree/59b7ff61134dfc11c4195e9201b2c1728ed4fcce

ACTUALIZACIÓN 2

Simplifiqué aún más mi script de prueba: 1. eliminando la clase y simplemente creando una lista de cadenas 2. eliminando myfunc() y usando del para eliminar el objeto mylist

 import sys import time def main(): size_kb = sys.argv[1] mylist = [] for x in xrange(100): mystr = ' ' * int(size_kb) * 1024 mylist.append(mystr) del mylist time.sleep(3600) if __name__ == '__main__': main() 

Mi script simplificado también da resultados similares al original. Sin embargo, si no creo una variable de cadena separada, no veo un aumento en la memoria. Aquí está el script que no crea un aumento en la memoria:

 import sys import time def main(): size_kb = sys.argv[1] mylist = [] for x in xrange(100): mylist.append(' ' * int(size_kb) * 1024) del mylist time.sleep(3600) if __name__ == '__main__': main() 

El entorno de prueba actualizado se encuentra aquí: https://github.com/saltycrane/debugging-python-memory-usage/tree/423ca6a50dccbe32572a9d0dea1068ddcb006663b

Más preguntas:

  • ¿Puede alguien más reproducir mis resultados?
  • ¿Se espera el aumento en la memoria por ps ?

Consejos sobre lo que está sucediendo.

Descubrí información interesante acerca de las “listas gratuitas” que parecen estar relacionadas con este problema:

  • ¿Por qué Python no libera la memoria cuando borro un objeto grande?
  • ¿Cómo puedo liberar explícitamente la memoria en Python?
  • Administración de memoria Python – Theano v0.6rc3 documentación

Desde el último enlace:

Para acelerar (y reutilizar) la asignación de memoria, Python utiliza una serie de listas para objetos pequeños. Cada lista contendrá objetos de tamaño similar

De hecho: si un elemento (de tamaño x) se desasigna (se libera por falta de referencia), su ubicación no se devuelve al conjunto de memoria global de Python (e incluso menos al sistema), sino que simplemente se marca como libre y se agrega a la lista gratuita de Artículos de talla x.

Si la memoria de objetos pequeños nunca se libera, la conclusión inevitable es que, al igual que los peces de colores, estas listas de objetos pequeños solo siguen creciendo, nunca disminuyendo, y que la huella de memoria de su aplicación está dominada por el mayor número de objetos pequeños asignados en un determinado punto.

ACTUALIZACIÓN 3

Simplifiqué demasiado el código en la Actualización 2. Al agregar la línea del mystr al final de la secuencia de comandos liberé la memoria. (Ver: https://github.com/saltycrane/debugging-python-memory-usage/blob/dd058e4774802cae7cbfca520fb835ea46b645e8/debug_memory_leaks.py )

Actualicé el script para que fuera lo suficientemente complicado para demostrar el problema. El problema aún existe en el siguiente código. El último código / entorno se encuentra aquí: https://github.com/saltycrane/debugging-python-memory-usage/tree/fc0c8ce9ba621cb86b6abb93adf1b297a7c0230b

 import gc import sys import time def main(): size_kb = sys.argv[1] mylist = [] for x in xrange(100): mystr = ' ' * int(size_kb) * 1024 mydict = {'mykey': mystr} mylist.append(mydict) del mystr del mydict del mylist gc.collect() time.sleep(3600) if __name__ == '__main__': main() 

También corrí el script en algunos otros entornos. El extraño resultado se estaba ejecutando desde dentro de un virtualenv limpio. En este caso, la caída de memoria ocurrió a 260KB en lugar de 128KB. Consulte https://github.com/saltycrane/debugging-python-memory-usage/tree/52fbd5d57ff45affdcd70623ddb74fa1f1ffbbc2

Entornos:

  • Ubuntu 12.04 de 64 bits, sistema Python 2.7.3: ejecución original
  • Ubuntu 12.04 de 64 bits, Python 3.3.0 comstackdo desde la fuente: resultados similares
  • Scientific Linux 6 de 64 bits, Python 2.6.6: resultados similares
  • Ubuntu 12.04 de 64 bits, Python 2.7.3 de un virtualenv: la caída de memoria ocurre a 260KB en lugar de 128KB

Más referencias:

  • http://revista.python.org.ar/2/en/html/memory-fragmentation.html
  • http://www.evanjones.ca/python-memory.html
  • http://mail.python.org/pipermail/python-dev/2004-October/049480.html (Nota: esto es de 2004)
  • http://mail.python.org/pipermail/python-dev/2006-March/061991.html
  • http://www.evanjones.ca/memoryallocator/
  • http://www.evanjones.ca/memory-allocator.pdf
  • http://hg.python.org/releasing/2.7.3/file/7bb96963d067/Objects/obmalloc.c

    Después de leer algunos de estos, veo una referencia a un “tamaño de arena” de 256 KB. Tal vez eso está relacionado?

ACTUALIZACIÓN 4 (MÁS SOLUCIONADO)

Schlenk descubrió la razón por la que el uso de la memoria se reduce a 128 KB . 128 KB es el punto en el que las “funciones de asignación de memoria” (malloc?) Utilizan mmap en lugar de boost la interrupción del progtwig usando sbrk. Curiosamente, el umbral se puede cambiar a través de una variable de entorno. MALLOC_MMAP_THRESHOLD_ una prueba configurando la variable de entorno MALLOC_MMAP_THRESHOLD_ a diferentes valores y la caída en el uso de la memoria coincidió con ese valor. Vea aquí los resultados: https://github.com/saltycrane/debugging-python-memory-usage/blob/97d93cd165a139a6b6f96720de63a92561dd2f05/output_debug_memory_leaks.py.txt

Aún me gustaría saber si se esperaba que el comportamiento de mi script perdiera memoria para valores de cadena <128 KB.

Algunos enlaces más:

  • mallopt (3) – Página de manual de Linux (desde schlenk)
  • Gestión de memoria Python y TCMalloc | Empujando la web
  • Re: Establezca x en Ninguno y del x no libera memoria en python 2.7.1 (HPUX 11.23, ia64) «python-list« ActiveState List Archives
  • Problema 3526: Implementación personalizada de malloc en SunOS y AIX – Rastreador de Python
  • Haga que el umbral mmap / brk en malloc sea dynamic para mejorar el rendimiento

Nota: De acuerdo con los últimos dos enlaces, hay un impacto de rendimiento (velocidad) para usar mmap en lugar de sbrk.

Simplemente puede golpear el comportamiento predeterminado del asignador de memoria de Linux.

Básicamente, Linux tiene dos estrategias de asignación, sbrk () para bloques pequeños de memoria y mmap () para bloques más grandes. Los bloques de memoria asignados a sbrk () no se pueden devolver fácilmente al sistema, mientras que los basados ​​en mmap () pueden (simplemente desasignar la página).

Entonces, si asigna un bloque de memoria mayor que el valor donde el asignador malloc () en su libc decide cambiar entre sbrk () y mmap (), verá este efecto. Consulte la llamada a mallopt (), especialmente el MMAP_THRESHOLD ( http://man7.org/linux/man-pages/man3/mallopt.3.html ).

Actualizar Para responder a su pregunta adicional: sí, se espera que pierda memoria de esa manera, si el asignador de memoria funciona como el libc en Linux. Si usara Windows LowFragmentationHeap en su lugar, probablemente no se filtraría, similar en AIX, dependiendo de qué malloc esté configurado. Tal vez uno de los otros asignadores (tcmalloc, etc.) también solucione estos problemas. sbrk () es increíblemente rápido, pero tiene problemas con la fragmentación de la memoria. CPython no puede hacer mucho al respecto, ya que no tiene un recolector de basura compacto, sino un simple recuento de referencias.

Python ofrece algunos métodos para reducir las asignaciones de búferes, consulte, por ejemplo, la publicación del blog aquí: http://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol -y-vistas de memoria /

Miraría en la recolección de basura. Es posible que los archivos más grandes desencadenen la recolección de basura con más frecuencia, pero los archivos pequeños se liberan pero permanecen colectivamente en algún umbral. Específicamente, llame a gc.collect () y luego llame a gc.get_referrers () en el objeto para, con suerte, revelar qué es lo que mantiene una instancia. Vea el documento de Python aquí:

http://docs.python.org/2/library/gc.html?highlight=gc#gc.get_referrers

Actualizar:

El problema se relaciona con la recolección de basura, el espacio de nombres y el conteo de referencias. La secuencia de comandos de bash que publicó está dando una visión bastante estrecha del comportamiento del recolector de basura. Pruebe un rango mayor y verá patrones en la cantidad de memoria que tomarán ciertos rangos. Por ejemplo, cambie el bash for loop para un rango mayor, algo como: seq 0 16 2056 .

Notó que el uso de la memoria se redujo si del mystr porque estaba eliminando cualquier referencia que le quedara. Es probable que se obtengan resultados similares si limita la variable mystr a su propia función de esta manera:

 def loopy(): mylist = [] for x in xrange(100): mystr = ' ' * int(size_kb) * 1024 mydict = {x: mystr} mylist.append(mydict) return mylist 

En lugar de usar scripts de bash, creo que podría obtener más información útil utilizando un generador de perfiles de memoria. Aquí hay un par de ejemplos usando Pympler . Esta primera versión es similar a su código de la Actualización 3:

 import gc import sys import time from pympler import tracker tr = tracker.SummaryTracker() print 'begin:' tr.print_diff() size_kb = sys.argv[1] mylist = [] mydict = {} print 'empty list & dict:' tr.print_diff() for x in xrange(100): mystr = ' ' * int(size_kb) * 1024 mydict = {x: mystr} mylist.append(mydict) print 'after for loop:' tr.print_diff() del mystr del mydict del mylist print 'after deleting stuff:' tr.print_diff() collected = gc.collect() print 'after garbage collection (collected: %d):' % collected tr.print_diff() time.sleep(2) print 'took a short nap after all that work:' tr.print_diff() mylist = [] print 'create an empty list for some reason:' tr.print_diff() 

Y la salida:

 $ python mem_test.py 256 begin: types | # objects | total size ======================= | =========== | ============= list | 957 | 97.44 KB str | 951 | 53.65 KB int | 118 | 2.77 KB wrapper_descriptor | 8 | 640 B weakref | 3 | 264 B member_descriptor | 2 | 144 B getset_descriptor | 2 | 144 B function (store_info) | 1 | 120 B cell | 2 | 112 B instancemethod | -1 | -80 B _sre.SRE_Pattern | -2 | -176 B tuple | -1 | -216 B dict | 2 | -1744 B empty list & dict: types | # objects | total size ======= | =========== | ============ list | 2 | 168 B str | 2 | 97 B int | 1 | 24 B after for loop: types | # objects | total size ======= | =========== | ============ str | 1 | 256.04 KB list | 0 | 848 B after deleting stuff: types | # objects | total size ======= | =========== | =============== list | -1 | -920 B str | -1 | -262181 B after garbage collection (collected: 0): types | # objects | total size ======= | =========== | ============ took a short nap after all that work: types | # objects | total size ======= | =========== | ============ create an empty list for some reason: types | # objects | total size ======= | =========== | ============ list | 1 | 72 B 

Observe que después del bucle for el tamaño total para la clase str es de 256 KB, esencialmente el mismo que el argumento que le pasé. Después de eliminar explícitamente la referencia a mystr en del mystr se libera la memoria. Después de esto, la basura ya se ha recogido, por lo que no hay más reducción después de gc.collect() .

La siguiente versión usa una función para crear un espacio de nombres diferente para la cadena.

 import gc import sys import time from pympler import tracker def loopy(): mylist = [] for x in xrange(100): mystr = ' ' * int(size_kb) * 1024 mydict = {x: mystr} mylist.append(mydict) return mylist tr = tracker.SummaryTracker() print 'begin:' tr.print_diff() size_kb = sys.argv[1] mylist = loopy() print 'after for loop:' tr.print_diff() del mylist print 'after deleting stuff:' tr.print_diff() collected = gc.collect() print 'after garbage collection (collected: %d):' % collected tr.print_diff() time.sleep(2) print 'took a short nap after all that work:' tr.print_diff() mylist = [] print 'create an empty list for some reason:' tr.print_diff() 

Y finalmente la salida de esta versión:

 $ python mem_test_2.py 256 begin: types | # objects | total size ======================= | =========== | ============= list | 958 | 97.53 KB str | 952 | 53.70 KB int | 118 | 2.77 KB wrapper_descriptor | 8 | 640 B weakref | 3 | 264 B member_descriptor | 2 | 144 B getset_descriptor | 2 | 144 B function (store_info) | 1 | 120 B cell | 2 | 112 B instancemethod | -1 | -80 B _sre.SRE_Pattern | -2 | -176 B tuple | -1 | -216 B dict | 2 | -1744 B after for loop: types | # objects | total size ======= | =========== | ============ list | 2 | 1016 B str | 2 | 97 B int | 1 | 24 B after deleting stuff: types | # objects | total size ======= | =========== | ============ list | -1 | -920 B after garbage collection (collected: 0): types | # objects | total size ======= | =========== | ============ took a short nap after all that work: types | # objects | total size ======= | =========== | ============ create an empty list for some reason: types | # objects | total size ======= | =========== | ============ list | 1 | 72 B 

Ahora, no tenemos que limpiar la estructura, y creo que este ejemplo muestra por qué es una buena idea usar funciones. La generación de código donde hay una gran parte en un espacio de nombres realmente impide que el recolector de basura haga su trabajo. No entrará en su casa y comenzará a asumir que las cosas son basura 🙂 Tiene que saber que las cosas son seguras de recolectar.

Ese enlace de Evan Jones es muy interesante por cierto.