python - ¿Por qué no se libera la memoria al sistema después de consultas grandes(o series de consultas) en django?




memory-leaks (2)

En primer lugar, DEBUG = False en settings.py, así que no, las connections['default'].queries no crecen y crecen hasta que se agota toda la memoria.

Comencemos con el hecho de que he cargado la tabla de User desde django.contrib.auth.models.User con 10000 usuarios (cada uno llamado 'test #' donde # es un número entre 1 y 10000).

Aquí está la vista:

from django.contrib.auth.models import User
from django.http import HttpResponse

import time

def leak(request):
    print "loading users"

    users = []
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())
    users += list(User.objects.all())

    print "sleeping"
    time.sleep(10)

    return HttpResponse('')

He adjuntado la vista anterior a /leak/ url e iniciar el servidor de desarrollo (con DEBUG = False, y lo he probado y no tiene nada que ver con la ejecución de un servidor de desarrollo frente a otras instancias).

Despues de correr:

% curl http://localhost:8000/leak/

La memoria del proceso del servidor de ejecución aumenta a alrededor del tamaño visto desde la salida ps aux continuación y luego permanece en ese nivel.

USER       PID %CPU %MEM    VSZ    RSS TTY      STAT START   TIME COMMAND
dlamotte 25694 11.5 34.8 861384 705668 pts/3    Sl+  19:11   2:52 /home/dlamotte/tmp/django-mem-leak/env/bin/python ./manage.py runserver

Entonces, ejecutar el comando de curl anterior no parece aumentar el uso de la memoria de la instancia (¿qué esperaba de una pérdida de memoria verdadera?), ¿Se debe reutilizar la memoria? Sin embargo, siento que hay un error aquí de que la memoria no se libera en el sistema (sin embargo, entiendo que puede ser un mejor rendimiento que Python NO libere la memoria).

Después de esto, ingenuamente intenté ver si Python liberaría grandes trozos de memoria que asignó. Así que intento lo siguiente desde una sesión de python:

>>> a = ''
>>> a += 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' * 10000000
>>> del a

La memoria se asigna en la línea a += ... como se esperaba, pero cuando sucede del a, la memoria se libera. ¿Por qué el comportamiento es diferente para los conjuntos de consultas django? ¿Es algo que django pretende hacer? ¿Hay alguna manera de cambiar este comportamiento?

Literalmente, pasé 2 días depurando este comportamiento sin saber a dónde ir a continuación (aprendí a usar guppy y objgraph que parecen no apuntar a nada interesante que pueda resolver).

ACTUALIZACIÓN: Esto podría ser simplemente la administración de memoria de Python en el trabajo y no tiene nada que ver con Django (sugerido en la lista de correo de usuarios de Django), pero me gustaría confirmación replicando esto de alguna manera en Python fuera de Django.

ACTUALIZACIÓN: usando la versión 2.6.5 de python


Decidí pasar mis comentarios a una respuesta para aclarar las cosas.

Desde Python 2.5, la asignación de memoria CPython rastrea el uso de la memoria interna por parte del asignador de objetos pequeños e intenta devolver arenas completamente libres al sistema operativo subyacente. Esto funciona la mayor parte del tiempo, pero el hecho de que los objetos no puedan moverse en la memoria significa que la fragmentación puede ser un problema grave.

Prueba el siguiente experimento (yo usé 3.2, pero 2.5+ debería ser similar si usas xrange):

# Create the big lists in advance to avoid skewing the memory counts
seq1 = [None] * 10**6 # Big list of references to None
seq2 = seq1[::10]

# Create and reference a lot of smaller lists
seq1[:] = [[] for x in range(10**6)] # References all the new lists
seq2[:] = seq1[::10] # Grab a second reference to 10% of the new lists

# Memory fragmentation in action
seq1[:] = [None] * 10**6 # 90% of the lists are no longer referenced here
seq2[:] = seq1[::10] # But memory freed only after last 10% are dropped

Tenga en cuenta que, incluso si seq1 las referencias a seq1 y seq2 , la secuencia anterior probablemente dejará su proceso de Python con una gran cantidad de memoria adicional.

Cuando la gente habla de que PyPy usa menos memoria que CPython, esta es una parte importante de lo que están hablando. Debido a que PyPy no usa referencias de puntero directo bajo el capó, puede usar un GC de compactación, evitando así gran parte del problema de fragmentación y devolviendo la memoria al sistema operativo de manera más confiable.


Muchas aplicaciones, tiempos de ejecución de idiomas y tal vez incluso algunos asignadores de memoria del sistema mantendrán la memoria desasignada en su lugar durante el mayor tiempo posible con el fin de reutilizarla, únicamente con fines de rendimiento. En un sistema complejo como Django, podría ser cualquier cantidad de extensiones, posiblemente implementadas en C, que muestren este comportamiento, o podría ser Python con algún tipo de conjunto de memoria o recolección de elementos no utilizados.

Incluso podría ser la implementación de malloc subyacente haciendo esto, o su sistema operativo manteniendo una cierta cantidad de espacio de memoria asignada a su proceso, incluso aunque el proceso no lo esté utilizando explícitamente. Sin embargo, no me cites en esto, ha pasado un tiempo desde que miré esas cosas.

Sin embargo, en general, si repetir el proceso de asignación después de la asignación inicial y el dealloc no duplica la cantidad de memoria utilizada, lo que está viendo no es una pérdida de memoria sino una agrupación de memoria. Es probable que solo sea un problema si tiene muchos procesos que compiten por una cantidad limitada de memoria en esa máquina.







memory-leaks