2009-11-02 13 views
8

Se dice que Python administra automáticamente la memoria. Estoy confundido porque tengo un programa de Python que consistentemente usa más de 2GB de memoria.¿Cómo escribir un programa Python eficiente en memoria?

Es un descargador y desempaquetador de datos binarios de varios hilos.

def GetData(url): 
    req = urllib2.Request(url) 
    response = urllib2.urlopen(req) 
    data = response.read() // data size is about 15MB 
    response.close() 
    count = struct.unpack("!I", data[:4]) 
    for i in range(0, count): 
     UNPACK FIXED LENGTH OF BINARY DATA HERE 
     yield (field1, field2, field3) 

class MyThread(threading.Thread): 
    def __init__(self, total, daterange, tickers): 
     threading.Thread.__init__(self) 

    def stop(self): 
     self._Thread__stop() 

    def run(self): 
     GET URL FOR EACH REQUEST 
     data = [] 
     items = GetData(url) 
     for item in items: 
      data.append(';'.join(item)) 
     f = open(filename, 'w') 
     f.write(os.linesep.join(data)) 
     f.close() 

Se están ejecutando 15 subprocesos. Cada solicitud obtiene 15MB de datos y los descomprime y guarda en un archivo de texto local. ¿Cómo podría este programa consumir más de 2GB de memoria? ¿Necesito hacer trabajos de reciclaje de memoria en este caso? ¿Cómo puedo ver cuánta memoria usan cada objeto o función?

Agradecería todos sus consejos o sugerencias sobre cómo mantener un programa de python ejecutándose en un modo de memoria eficiente.

Editar: Aquí está la salida de "cat/proc/meminfo"

MemTotal:  7975216 kB 
MemFree:   732368 kB 
Buffers:   38032 kB 
Cached:   4365664 kB 
SwapCached:  14016 kB 
Active:   2182264 kB 
Inactive:  4836612 kB 
+3

primera pregunta sería: ¿Cuánta memoria física tiene disponible? Simplemente podría ser que Python decide que no vale la pena limpiar porque de todos modos todavía hay 1GB de memoria física libre. –

+0

Pruebe el uso de la memoria de creación de perfiles: http://stackoverflow.com/questions/110259/python-memory-profiler – outis

+0

@Matthew, Parece que solo hay 730 MB de 8 GB de RAM disponibles. – jack

Respuesta

10

Como otros han dicho, es necesario al menos los siguientes dos cambios:

  1. No cree una enorme lista de números enteros con range

    # use xrange 
    for i in xrange(0, count): 
        # UNPACK FIXED LENGTH OF BINARY DATA HERE 
        yield (field1, field2, field3) 
    
  2. no crean una cadena enorme como el cuerpo completo del archivo que se escribirá a la vez

    # use writelines 
    f = open(filename, 'w') 
    f.writelines((datum + os.linesep) for datum in data) 
    f.close() 
    

Aún mejor, se podría escribir el archivo como:

items = GetData(url) 
    f = open(filename, 'w') 
    for item in items: 
     f.write(';'.join(item) + os.linesep) 
    f.close() 
2

Puede que este programa sea más eficiente de la memoria por no leer todos los 15 MB de la conexión TCP, pero en lugar de procesar cada línea como se lee Esto hará que los servidores remotos te esperen, por supuesto, pero está bien.

Python no es muy eficiente en cuanto a la memoria. No fue construido para eso.

5

La última línea seguramente debería ser f.close()? Esos parens que siguen son un poco importantes.

+0

Sí, es f.close(). Fue un error tipográfico en la publicación original. – jack

2

Usted podría hacer más de su trabajo en el código C compilado si convierte esto a una lista por comprensión:

data = [] 
items = GetData(url) 
for item in items: 
    data.append(';'.join(item)) 

a:

data = [';'.join(items) for items in GetData(url)] 

Esto es en realidad un poco diferente a su código original. En su versión, GetData devuelve un 3-tuple, que vuelve en los artículos. A continuación, itere sobre este triplete y añada ';'. Join (elemento) para cada elemento del mismo. Esto significa que obtiene 3 entradas añadidas a los datos por cada lectura de triplete de GetData, cada una ';'. Join'ed. Si los artículos son solo cadenas, entonces ';'. Join te devolverá una cadena con cada otro carácter a ';' - eso es ';'. join ("ABC") devolverá "A; B; C". Creo que lo que de hecho quería era guardar cada triplete de nuevo en la lista de datos como los 3 valores del triplete, separados por punto y coma. Eso es lo que genera mi versión.

Esto también puede ayudar un poco con su problema de memoria original, ya que no está creando más valores de Python. Recuerde que una variable en Python tiene mucho más sobrecarga que uno en un lenguaje como C. Como cada valor es en sí mismo un objeto, y agrega la sobrecarga de cada referencia de nombre a ese objeto, puede expandir fácilmente el requisito de almacenamiento teórico varios- doblez. En su caso, leyendo 15Mb X 15 = 225Mb + la sobrecarga de cada elemento de cada triple que se almacena como una entrada de cadena en su lista de datos podría crecer rápidamente a su tamaño observado de 2Gb. Como mínimo, mi versión de su lista de datos tendrá solo 1/3 de las entradas, más las referencias de elementos separadas se saltan, además la iteración se realiza en código compilado.

6

Considere el uso de xrange() en lugar de range(), creo que xrange es un generador mientras que range() expande toda la lista.

Yo diría que o bien no se lee todo el archivo en la memoria, o no se guarda toda la estructura desempaquetada en la memoria.

Actualmente mantiene ambos en la memoria, al mismo tiempo, esto va a ser bastante grande. De modo que tiene al menos dos copias de sus datos en la memoria, más algunos metadatos.

También la línea final

f.write(os.linesep.join(data)) 

realidad puede significar que usted tiene una tercera copia temporalmente en la memoria (una gran cadena con todo el archivo de salida).

Así que diría que lo está haciendo de una manera bastante ineficiente, manteniendo todo el archivo de entrada, todo el archivo de salida y una buena cantidad de datos intermedios en la memoria a la vez.

Usar el generador para analizar es una buena idea. Considere escribir cada registro después de haberlo generado (luego puede descartarse y reutilizarse la memoria), o si eso genera demasiadas solicitudes de escritura, bátelas en, por ejemplo, 100 filas a la vez.

Del mismo modo, la lectura de la respuesta se puede hacer en trozos. Como son registros fijos, esto debería ser razonablemente fácil.

8

El principal culpable aquí es como se mencionó anteriormente la llamada range(). Creará una lista con 15 millones de miembros, y eso consumirá 200 MB de tu memoria, y con 15 procesos, eso es 3GB.

Pero tampoco se lee en todo el archivo de 15MB en datos(), se lee bit a bit de la respuesta. Pegar esos 15MB en una variable consumirá hasta 15 MB de memoria más que leer bit a bit de la respuesta.

Es posible que desee considerar simplemente extraer datos hasta que se agote si son indata, y comparar el recuento de datos que extrajo con lo que los primeros bytes dijeron que debería ser. Entonces no necesita range() ni xrange(). Me parece más pitónico. :)

+1

en realidad, el conteo de variables aquí no está cerca de los 15 millones de rango porque cada porción de datos binarios es de 30 bytes, por lo que no creará una lista con 15 millones de elementos. – jack

+0

Ah, ya veo. Aún así, ayudará tanto como no leer todos los datos en 'datos', entonces. –

2

Hay 2 lugares obvios en las que guarda objetos de datos grandes en la memoria (data variable en GetData() y data en MyThread.run() - estos dos tomará alrededor de 500 millones de barriles) y probablemente hay otros lugares en el código omitido. Ambos son fáciles de hacer eficiente la memoria. Use response.read(4) en lugar de leer toda la respuesta a la vez y hágalo de la misma manera en el código detrás de UNPACK FIXED LENGTH OF BINARY DATA HERE. Cambiar data.append(...) en MyThread.run() a

if not first: 
    f.write(os.linesep) 
f.write(';'.join(item)) 

Estos cambios le ahorrará una gran cantidad de memoria.

1

Asegúrese de eliminar los hilos después de que se detengan.(Usando del)

Cuestiones relacionadas