¿Es posible tener una pérdida de memoria real en Python debido a su código?

10 minutos de lectura

avatar de usuario
orokusaki

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.

  • 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

avatar de usuario
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 de sys.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 por a. ¡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

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad