python: compartiendo enormes diccionarios usando multiprocesamiento

Estoy procesando grandes cantidades de datos, almacenados en un diccionario, utilizando multiprocesamiento. Básicamente, todo lo que estoy haciendo es cargar algunas firmas, almacenadas en un diccionario, construir un objeto dict compartido a partir de él (obtener el objeto ‘proxy’ devuelto por Manager.dict ()) y pasar este proxy como argumento a la función que tiene Para ser ejecutado en multiprocesamiento.

Solo para aclarar:

signatures = dict() load_signatures(signatures) [...] manager = Manager() signaturesProxy = manager.dict(signatures) [...] result = pool.map ( myfunction , [ signaturesProxy ]*NUM_CORES ) 

Ahora, todo funciona perfectamente si las firmas son menos de 2 millones de entradas o menos. De todos modos, tengo que procesar un diccionario con claves de 5.8M (las firmas de decapado en formato binario generan un archivo de 4.8 GB). En este caso, el proceso muere durante la creación del objeto proxy:

 Traceback (most recent call last): File "matrix.py", line 617, in  signaturesProxy = manager.dict(signatures) File "/usr/lib/python2.6/multiprocessing/managers.py", line 634, in temp token, exp = self._create(typeid, *args, **kwds) File "/usr/lib/python2.6/multiprocessing/managers.py", line 534, in _create id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds) File "/usr/lib/python2.6/multiprocessing/managers.py", line 79, in dispatch raise convert_to_error(kind, result) multiprocessing.managers.RemoteError: --------------------------------------------------------------------------- Traceback (most recent call last): File "/usr/lib/python2.6/multiprocessing/managers.py", line 173, in handle_request request = c.recv() EOFError --------------------------------------------------------------------------- 

Sé que la estructura de datos es enorme, pero estoy trabajando en una máquina equipada con 32GB de RAM, y en la parte superior veo que el proceso, después de cargar las firmas, ocupa 7GB de RAM. Luego comienza a construir el objeto proxy y el uso de RAM aumenta hasta ~ 17GB de RAM, pero nunca se acerca a 32. En este punto, el uso de RAM comienza a disminuir rápidamente y el proceso termina con el error anterior. Así que supongo que esto no se debe a un error de memoria insuficiente …

¿Alguna idea o sugerencia?

Gracias,

Davide

Si los diccionarios son de solo lectura, no necesita objetos proxy en la mayoría de los sistemas operativos.

Simplemente cargue los diccionarios antes de comenzar con los trabajadores y colóquelos en un lugar al que puedan acceder; El lugar más simple es globalmente a un módulo. Serán legibles por los trabajadores.

 from multiprocessing import Pool buf = "" def f(x): buf.find("x") return 0 if __name__ == '__main__': buf = "a" * 1024 * 1024 * 1024 pool = Pool(processes=1) result = pool.apply_async(f, [10]) print result.get(timeout=5) 

Esto solo utiliza 1 GB de memoria combinada, no 1 GB para cada proceso, ya que cualquier sistema operativo moderno hará una copia de copia en escritura de los datos creados antes de la bifurcación. Solo recuerde que los cambios en los datos no serán vistos por otros trabajadores, y la memoria, por supuesto, se asignará a los datos que cambie.

Usará algo de memoria: la página de cada objeto que contiene el recuento de referencia se modificará, por lo que se asignará. Si esto importa depende de los datos.

Esto funcionará en cualquier sistema operativo que implemente la bifurcación ordinaria. No funcionará en Windows; su modelo de proceso (paralizado) requiere relanzar todo el proceso para cada trabajador, por lo que no es muy bueno para compartir datos.

¿Por qué no intentas esto con una base de datos? Las bases de datos no se limitan a ramios físicos / direccionables y son seguras para el uso de procesos múltiples.

Para ahorrar tiempo y no tener que depurar problemas a nivel del sistema, tal vez podría dividir su diccionario de registro de 5.8 millones en tres conjuntos de ~ 2 millones cada uno, y ejecutar el trabajo 3 veces.

Creo que el problema que encontraste fue el dictado o hash table redimensionándose a medida que crece. Inicialmente, el dict tiene un número establecido de cubetas disponibles. No estoy seguro de Python, pero sé que Perl comienza con 8 y luego, cuando los cubos están llenos, el hash es recreado por 8 más (es decir, 8, 16, 32, …).

El cubo es una ubicación de aterrizaje para el algoritmo hash. Las 8 ranuras no significan 8 entradas, significa 8 ubicaciones de memoria. Cuando se agrega el nuevo elemento, se genera un hash para esa clave, luego se almacena en ese cubo.

Aquí es donde entran en juego las colisiones. Cuantos más elementos haya en un depósito, más lenta será la función, ya que los elementos se adjuntan secuencialmente debido al tamaño dynamic de la ranura.

Un problema que puede ocurrir es que sus claves son muy similares y producen el mismo resultado de hash, lo que significa que la mayoría de las claves están en una ranura. La asignación previa de los bloques de hash ayudará a eliminar esto y, de hecho, mejorará el tiempo de procesamiento y la administración de claves, y además ya no tendrá que hacer todo ese intercambio.

Sin embargo, creo que todavía está limitado a la cantidad de memoria contigua libre y eventualmente necesitará ir a la solución de base de datos.

nota al margen: todavía soy nuevo en Python, sé que en Perl puede ver las estadísticas de hash haciendo un% HASHNAME impreso, que mostrará su distribución del uso del depósito. Lo ayuda a identificar los recuentos de colisiones, en caso de que necesite asignar previamente los depósitos. ¿Se puede hacer esto también en Python?

Rico