No tengo un ejemplo de código, pero tengo curiosidad por saber si es posible escribir código de Python que resulte esencialmente en una pérdida de memoria.
¿Es posible tener una pérdida de memoria real en Python debido a su código?
orokusaki
Crasta
Es posible, si.
Depende de qué tipo de pérdida de memoria estés hablando. Dentro del código Python puro, no es posible “olvidarse de liberar” la memoria como en C, pero es posible dejar una referencia colgando en alguna parte. Algunos ejemplos de tales:
un objeto de rastreo no controlado que mantiene vivo un marco de pila completo, aunque la función ya no se está ejecutando
while game.running():
try:
key_press = handle_input()
except SomeException:
etype, evalue, tb = sys.exc_info()
# Do something with tb like inspecting or printing the traceback
En este ejemplo tonto de un bucle de juego tal vez, asignamos ‘tb’ a un local. Teníamos buenas intenciones, pero este tb contiene información de marco sobre la pila de lo que sea que estaba sucediendo en nuestro handle_input hasta lo que esto llama. Suponiendo que su juego continúe, este ‘tb’ se mantiene vivo incluso en su próxima llamada a handle_input, y tal vez para siempre. los documentos para exc_info ahora hable sobre este posible problema de referencia circular y recomiende simplemente no asignar tb
si no lo necesitas absolutamente. Si necesita obtener un rastreo, considere, por ejemplo, traceback.format_exc
almacenar valores en una clase o alcance global en lugar de un alcance de instancia, y no darse cuenta.
Esto puede suceder de manera insidiosa, pero a menudo sucede cuando define tipos mutables en el ámbito de su clase.
class Money(object):
name=""
symbols = [] # This is the dangerous line here
def set_name(self, name):
self.name = name
def add_symbol(self, symbol):
self.symbols.append(symbol)
En el ejemplo anterior, digamos que lo hiciste
m = Money()
m.set_name('Dollar')
m.add_symbol('$')
probablemente encontrarás este error en particular rápidamente, pero en este caso coloca un valor mutable en el alcance de la clase y, aunque accede correctamente a él en el alcance de la instancia, en realidad está “cayendo” en el objeto de clase‘s __dict__
.
Esto, usado en ciertos contextos, como contener objetos, podría causar cosas que harían que el montón de su aplicación creciera para siempre y causaría problemas, por ejemplo, en una aplicación web de producción que no reiniciaba sus procesos de vez en cuando.
Referencias cíclicas en clases que también tienen un __del__
método.
Irónicamente, la existencia de un __del__
hace imposible que el recolector de basura cíclico limpie una instancia. Digamos que tenía algo en lo que quería hacer un destructor para fines de finalización:
class ClientConnection(...):
def __del__(self):
if self.socket is not None:
self.socket.close()
self.socket = None
Ahora bien, esto funciona bien por sí solo, y se le puede hacer creer que está siendo un buen administrador de los recursos del sistema operativo para asegurarse de que el zócalo sea ‘desechado’.
Sin embargo, si ClientConnection mantuvo una referencia para decir, User
y el usuario mantuvo una referencia a la conexión, es posible que tenga la tentación de decir que en la limpieza, hagamos que el usuario elimine la referencia de la conexión. Este es en realidad el defecto.sin embargo: el GC cíclico no conoce el orden correcto de las operaciones y no puede limpiarlo.
La solución a esto es asegurarse de realizar la limpieza en, por ejemplo, desconectar eventos llamando a algún tipo de cierre, pero nombre ese método de otra manera que no sea __del__
.
Extensiones de C mal implementadas, o no usar correctamente las bibliotecas de C como se supone que deben ser.
En Python, confías en el recolector de basura para tirar las cosas que no estás usando. Pero si usa una extensión de C que envuelve una biblioteca de C, la mayoría de las veces usted es responsable de asegurarse de cerrar o desasignar recursos explícitamente. En su mayoría, esto está documentado, pero un programador de Python que está acostumbrado a no tener que hacer esta desasignación explícita podría desechar el identificador (como regresar de una función o lo que sea) a esa biblioteca sin saber que se están reteniendo los recursos.
Ámbitos que contienen cierres que contienen mucho más de lo que podría haber anticipado
class User:
def set_profile(self, profile):
def on_completed(result):
if result.success:
self.profile = profile
self._db.execute(
change={'profile': profile},
on_complete=on_completed
)
En este ejemplo artificial, parece que estamos usando algún tipo de llamada ‘asincrónica’ que nos devolverá la llamada en on_completed
cuando se realiza la llamada DB (la implementación podría haber sido promesas, termina con el mismo resultado).
Lo que quizás no te des cuenta es que el on_completed
El cierre vincula una referencia a self
para ejecutar la self.profile
asignación. Ahora, tal vez el cliente de la base de datos realiza un seguimiento de las consultas activas y apunta a los cierres para llamar cuando hayan terminado (ya que es asíncrono) y decir que falla por cualquier motivo. Si el cliente de base de datos no limpia correctamente las devoluciones de llamada, etc., en este caso, el cliente de base de datos ahora tiene una referencia a on_completed que tiene una referencia a Usuario que mantiene un _db
– Ahora ha creado una referencia circular que es posible que nunca se recopile.
(Incluso sin una referencia circular, el hecho de que los cierres vinculan locales e incluso instancias a veces puede causar que los valores que pensó que se recopilaron estuvieran vivos durante mucho tiempo, lo que podría incluir sockets, clientes, grandes búferes y árboles completos de cosas)
Parámetros predeterminados que son tipos mutables
def foo(a=[]):
a.append(time.time())
return a
Este es un ejemplo artificial, pero se podría hacer creer que el valor predeterminado de a
ser una lista vacía significa añadir a ella, cuando en realidad es una referencia a la mismo lista. Esto nuevamente podría causar un crecimiento ilimitado sin saber que lo hiciste.
-
¿Podría elaborar o proporcionar alguna documentación sobre su primera viñeta “un objeto de rastreo no controlado que mantiene vivo un marco de pila completo, aunque la función ya no se está ejecutando”? De hecho, tengo una fuga de rastreo, pero no puedo ver por qué se hace referencia a ella en cualquier lugar (stackoverflow.com/questions/44681681/…)
– BiAiB
22 de junio de 2017 a las 8:29
-
@BiAiB El simple y simple es que puede tener un objeto de rastreo almacenado en algún lugar. Por ejemplo,
sys.last_traceback
mantiene una referencia al último rastreo e información desys.exc_info()
hace parecido. Esto se complica aún más con los enlaces entre idiomas, etc. Saltaré a su pregunta y responderé más.– Crasta
31 de agosto de 2017 a las 6:55
-
La fuga de memoria es una memoria asignada a la que perdió la referencia y, por cierto, no se puede desasignar. Estropear los módulos sys y gc crearía algunas fugas de memoria. ¡No he estado ahi! Yo tampoco quiero ir. @Crast
del foo
es suficiente para liberar la memoria asignada pora
. ¡No veo ninguna pérdida de memoria allí!– Elis Byberi
18 de diciembre de 2017 a las 22:03
La definición clásica de una fuga de memoria es la memoria que se usó una vez y ahora no se usa, pero no se recuperó. Eso es casi imposible con código Python puro. Pero como señala Antoine, fácilmente puede tener el efecto de consumir toda su memoria sin darse cuenta al permitir que las estructuras de datos crezcan sin límite, incluso si no necesita mantener todos los datos.
Con las extensiones C, por supuesto, estás de vuelta en un territorio no administrado y todo es posible.
Por supuesto que puede. El ejemplo típico de una fuga de memoria es si crea un caché que nunca vacía manualmente y que no tiene una política de desalojo automático.
-
Técnicamente, eso no es una pérdida de memoria ya que la aplicación aún está poder para liberar la memoria, aunque elige no hacerlo.
– Justin
7 de enero de 2010 a las 0:37
-
Bueno, creo que definimos “pérdida de memoria” de manera diferente, entonces. Para mí una fuga es una fuga, independientemente de si es posible repararla o no.
– Antonio P.
7 de enero de 2010 a las 0:39
-
Si no tiene un código para eliminar elementos del caché, entonces no puede eliminarlos, ¿verdad?
– Laurence Gonsalves
7 de enero de 2010 a las 0:58
-
Creo que una fuga de memoria es cualquier cosa que no libera memoria cuando termina de cumplir su propósito. Sin embargo, no llamaría a un caché administrado incorrectamente una pérdida de memoria, solo porque realmente no está hecho. Es un desperdicio.
– orokusaki
7 de enero de 2010 a las 1:19
-
Según su definición, un proceso de ejecución prolongada no puede tener una pérdida de memoria hasta que esté “terminado”, lo que me suena un poco inadecuado.
– Antonio P.
7 de enero de 2010 a las 15:55
En el sentido de dejar huérfanos los objetos asignados después de que quedan fuera del alcance porque se olvidó de desasignarlos, no; Python desasignará automáticamente los objetos fuera del alcance (Recolección de basura). Pero en el sentido del que habla @Antione, sí.
Dado que muchos módulos están escritos en C, sí, es posible tener pérdidas de memoria. imagine que está utilizando un contexto de dibujo de pintura gui (por ejemplo, con wxpython), puede crear búferes de memoria, pero si olvidó liberarlo. tendrá pérdidas de memoria… en este caso, las funciones de C++ de wx api están ajustadas a python.
un uso incorrecto mayor, imagine que sobrecarga estos métodos de widgets wx dentro de python … fugas de memoria aseguradas.
Creo un objeto con un atributo pesado para mostrar en el uso de la memoria del proceso.
Luego creo un diccionario que se refiere a sí mismo una gran cantidad de veces.
Luego elimino el objeto y le pido a GC que recolecte basura. No recoge ninguno.
Luego compruebo la huella de RAM del proceso: es lo mismo.
¡Aquí tienes, pérdida de memoria!
α python
Python 2.7.15 (default, Oct 2 2018, 11:47:18)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import gc
>>> class B(object):
... b = list(range(1 * 10 ** 8))
...
>>>
[1]+ Stopped python
~/Sources/plan9port [git branch:master]
α ps aux | grep python
alexander.pugachev 85164 0.0 19.0 7562952 3188184 s010 T 2:08pm 0:03.78 /usr/local/Cellar/[email protected]/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
~/Sources/plan9port [git branch:master]
α fg
python
>>> b = B()
>>> for i in range(1000):
... b.a = {'b': b}
...
>>>
[1]+ Stopped python
~/Sources/plan9port [git branch:master]
α ps aux | grep python
alexander.pugachev 85164 0.0 19.0 7579336 3188264 s010 T 2:08pm 0:03.79 /usr/local/Cellar/[email protected]/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
~/Sources/plan9port [git branch:master]
α fg
python
>>> b.a['b'].a
{'b': <__main__.B object at 0x109204950>}
>>> del(b)
>>> gc.collect()
0
>>>
[1]+ Stopped python
~/Sources/plan9port [git branch:master]
α ps aux | grep python
alexander.pugachev 85164 0.0 19.0 7579336 3188268 s010 T 2:08pm 0:05.13 /usr/local/Cellar/[email protected]/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
Debe aclarar: ¿está hablando de pérdida de memoria después de que el proceso termina de ejecutarse? ¿Y estás hablando de Python puro sin módulos C?
– Michael Greene
7 de enero de 2010 a las 0:30