2010-06-11 27 views
9

Necesito optimizar el uso de RAM de mi aplicación.
POR FAVOR, evíteme las conferencias diciéndome que no me debería importar la memoria al codificar Python. Tengo un problema de memoria porque uso diccionarios predeterminados muy grandes (sí, también quiero ser rápido). Mi consumo de memoria actual es de 350 MB y está creciendo. Ya no puedo usar el alojamiento compartido y si mi Apache abre más procesos, la memoria se duplicará y triplicará ... y es costoso.
He hecho perfiles extensos y sé exactamente dónde están mis problemas.
Tengo varios diccionarios grandes (> 100 000 entradas) con claves Unicode. Un diccionario comienza en 140 bytes y crece rápidamente, pero el problema más grande es las claves. Python optimiza las cadenas en la memoria (o eso he leído) para que las búsquedas puedan ser comparaciones de ID ('interning'). No estoy seguro de que esto también sea cierto para las cadenas Unicode (no pude 'internarlas').
Los objetos almacenados en el diccionario son listas de tuplas (un_objeto, un int, un int).Python consejos para la optimización de la memoria

my_big_dict [some_unicode_string] .Append ((mi_objeto, an_int, another_int))

que ya encontraron que vale la pena para dividir a varios diccionarios porque las tuplas toman mucho espacio .. .
¡Descubrí que podía ahorrar memoria RAM mezclando las cadenas antes de usarlas como claves! Pero luego, por desgracia, me encontré con colisiones de cumpleaños en mi sistema de 32 bits. (Pregunta lateral: ¿hay un diccionario de teclas de 64 bits que pueda usar en un sistema de 32 bits?)

Python 2.6.5 en Linux (producción) y Windows. ¿Algún consejo sobre la optimización del uso de memoria de diccionarios/listas/tuplas? Incluso pensé en usar C - No me importa si este pequeño fragmento de código es feo. Es solo un lugar singular.

¡Gracias de antemano!

+0

2 pequeños comentarios: me gustan mucho las respuestas listas para usar del sistema, pero ¿pueden (por ejemplo, una base de datos, incluso en caché) comparar realmente el rendimiento con un diccionario de Python? Estoy ejecutando algoritmos en tiempo real y el diccionario es apenas lo suficientemente rápido. Ciertamente probaré Memcached y Redis (genial) pero ¿las comunicaciones entre procesos serán lo suficientemente rápidas para mí? (Lo siento por agregar esto ahora. Difícil de optimizar tanto para la memoria como para la velocidad ...) Además, mi diccionario es principalmente de solo lectura. ¿Puedo usar este conocimiento de alguna manera? –

+1

"PEP 412: Key-Sharing Dictionary" puede ser de su interés. Creo que está incluido en Python 3.3 http://www.python.org/dev/peps/pep-0412/ – bcoughlan

+0

@bcoughlan muy genial, gracias! Lamentablemente, tengo que esperar un 2.7 backport. –

Respuesta

10

Sugiero lo siguiente: almacene todos los valores en una base de datos y conserve un diccionario en la memoria con cadenas hash como claves. Si ocurre una colisión, obtenga valores de la base de datos, de lo contrario (la gran mayoría de los casos) utiliza el diccionario. Efectivamente, será un caché gigante.

Un problema con los diccionarios en Python es que usan mucho espacio: incluso un diccionario int-int usa 45-80 bytes por pares clave-valor en un sistema de 32 bits. Al mismo tiempo, un array.array('i') utiliza solo 8 bytes por un par de entradas, y con un poco de contabilidad se puede implementar un diccionario razonablemente rápido basado en arreglos int → int.

Una vez que tenga una implementación eficiente de la memoria un diccionario int-int, dividir su cadena → (objeto, int, int) diccionario en tres diccionarios y el uso hashes en lugar de cadenas completas. Obtendrá un int → object y dos int → int diccionarios. Emule el diccionario int → del siguiente modo: conserve una lista de objetos y almacene los índices de los objetos como valores de un int → int diccionario.

Me doy cuenta de que hay una cantidad considerable de codificación involucrada para obtener un diccionario basado en arreglos. Tuve un problema similar al suyo y he implementado un diccionario hash-int genérico razonablemente rápido, muy eficiente en memoria. Here's my code (licencia BSD). Está basado en arreglos (8 bytes por par), se ocupa de la comprobación de colisiones y hash de claves, mantiene la matriz (varias matrices más pequeñas, en realidad) encargada durante las escrituras y realiza búsquedas binarias en las lecturas. Su código se reduce a algo como:

dictionary = HashIntDict(checking = HashIntDict.CHK_SHOUTING) 
# ... 
database.store(k, v) 
try: 
    dictionary[k] = v 
except CollisionError: 
    pass 
# ... 
try: 
    v = dictionary[k] 
except CollisionError: 
    v = database.fetch(k) 

checking El parámetro especifica lo que sucede cuando se produce una colisión: CHK_SHOUTING plantea CollisionError en lecturas y escrituras, CHK_DELETING vuelve None en las lecturas y guarda silencio sobre las escrituras, no hace CHK_IGNORING verificación de colisión

Lo que sigue es una breve descripción de mi implementación, ¡los consejos de optimización son bienvenidos! La estructura de datos de nivel superior es un diccionario regular de matrices. Cada matriz contiene hasta 2^16 = 65536 pares enteros (raíz cuadrada de 2^32). Una clave k y un valor correspondiente v se almacenan en k/65536 -th array. Las matrices se inicializan bajo demanda y se guardan ordenadas por claves. La búsqueda binaria se ejecuta en cada lectura y escritura. La verificación de colisión es una opción.Si está habilitado, un intento de sobrescribir una clave ya existente eliminará la clave y el valor asociado del diccionario, agregará la clave a un conjunto de claves colisionantes y (de nuevo, de manera opcional) generará una excepción.

1

Utilice shelve o una base de datos para almacenar los datos en lugar de un dict en memoria.

2

He tenido situaciones en las que he tenido una colección de objetos grandes que he necesitado para clasificar y filtrar por diferentes métodos basados ​​en varias propiedades de metadatos. No necesitaba las partes más grandes de ellas, así que las descargué en el disco.

Como sus datos son de un tipo tan simple, una base de datos SQLite rápida podría resolver todos sus problemas, incluso acelerar un poco las cosas.

4

Para una aplicación web debe usar una base de datos, la forma en que lo hace está creando una copia de su dict para cada proceso de apache, lo cual es extremadamente inútil. Si tiene suficiente memoria en el servidor, la tabla de la base de datos se almacenará en la memoria (si no tiene suficiente para una copia de la tabla, coloque más RAM en el servidor). Solo recuerde poner los índices correctos en su tabla de base de datos o obtendrá un mal rendimiento.

0

Si desea realizar una optimización exhaustiva y tener un control total sobre el uso de la memoria, también puede escribir un módulo C/C++. Usando Swig, el ajuste del código en Python se puede hacer fácilmente, con una pequeña sobrecarga de rendimiento en comparación con el módulo C Python puro.

1

Si desea permanecer con el almacén de datos en memoria, puede intentar algo como memcached.

De esta forma, puede usar una única clave/valor-store en memoria de todos los procesos de Python.

Hay varias bibliotecas cliente de memcached python.

+3

memcached tiene pérdidas, por lo que no es adecuado para un almacén de datos. –

1

Redis sería una gran opción aquí si tiene la opción de usarlo en un host compartido, similar a memcached, pero optimizado para estructuras de datos. Redis también es compatible con enlaces de python.

Lo uso día a día para el procesamiento de números, pero también en sistemas de producción como almacén de datos y no puedo recomendarlo lo suficiente.

Además, ¿tiene una opción para proxy su aplicación detrás de nginx en lugar de utilizar Apache? Puede encontrar (si está permitido) que esta disposición de proxy/webapp tenga menos recursos.

Buena suerte.

Cuestiones relacionadas