textiowrapper open libreria got expected bytes argument python memory io

python - open - string argument expected, got ''bytes''



Minimizar la lectura y la escritura en el disco en Python para una operación con gran capacidad de memoria (11)

Fondo

Estoy trabajando en un proyecto bastante computacionalmente intensivo para un proyecto de lingüística computacional, pero el problema que tengo es bastante general y, por lo tanto, espero que una solución también sea interesante para otros.

Requerimientos

El aspecto clave de este programa en particular que debo escribir es que debe:

  1. Lee un corpus grande (entre 5G y 30G, y cosas potencialmente más grandes en la línea)
  2. Procesa los datos en cada línea.
  3. A partir de estos datos procesados, construya un gran número de vectores (la dimensionalidad de algunos de estos vectores es> 4,000,000). Típicamente se está construyendo cientos de miles de tales vectores.
  4. Todos estos vectores deben guardarse en el disco en un formato u otro.

Los pasos 1 y 2 no son difíciles de realizar de manera eficiente: solo use generadores y tenga un canal de análisis de datos. El gran problema es la operación 3 (y por conexión 4)

Paréntesis: Detalles técnicos

En caso de que el procedimiento real para la construcción de vectores afecte a la solución:

Para cada línea en el corpus, uno o más vectores deben tener sus pesos base actualizados.

Si piensa en ellas en términos de listas de python, cada línea, cuando se procesa, actualiza una o más listas (creándolas si es necesario) incrementando los valores de estas listas en uno o más índices en un valor (que puede diferir según la índice).

Los vectores no dependen unos de otros, ni importa en qué orden se leen las líneas del corpus.

Soluciones intentadas

Hay tres extremos cuando se trata de cómo hacer esto:

  1. Podría construir todos los vectores en la memoria. Luego escríbelos en el disco.
  2. Podría construir todos los vectores directamente en el disco, usando un estante de salmuera o alguna de esas bibliotecas.
  3. Podía construir los vectores en memoria uno a la vez y escribirlos en el disco, pasando a través del cuerpo una vez por vector.

Todas estas opciones son bastante intratables. 1 solo usa toda la memoria del sistema, y ​​entra en pánico y se ralentiza a un rastreo. 2 es demasiado lento ya que las operaciones IO no son rápidas. 3 es posiblemente incluso más lento que 2 por las mismas razones.

Metas

Una buena solución implicaría:

  1. Construyendo tanto como sea posible en la memoria.
  2. Una vez que la memoria está llena, volcar todo en el disco.
  3. Si se necesitan bits del disco nuevamente, recupérelos de nuevo en la memoria para agregar elementos a esos vectores.
  4. Vuelve a 1 hasta que todos los vectores estén construidos.

El problema es que no estoy muy seguro de cómo hacerlo. Parece un tanto untípico preocuparse por los atributos del sistema como la memoria RAM, pero no veo cómo este tipo de problema puede resolverse de manera óptima sin tener esto en cuenta. Como resultado, realmente no sé cómo empezar con este tipo de cosas.

Pregunta

¿Alguien sabe cómo resolver este tipo de problemas? ¿Python simplemente no es el lenguaje correcto para este tipo de cosas? ¿O existe una solución simple para maximizar la cantidad que se hace desde la memoria (dentro de lo razonable) al tiempo que se minimizan las veces que se deben leer los datos del disco o escribir en él?

Muchas gracias por vuestra atencion. Espero ver lo que las mentes brillantes de stackoverflow pueden lanzar en mi camino.

Detalles adicionales

El tipo de máquina en la que se ejecuta este problema generalmente tiene más de 20 núcleos y ~ 70G de RAM. El problema puede ser paralelizado (à la MapReduce) en el sentido de que los vectores separados para una entidad pueden construirse a partir de segmentos del cuerpo y luego agregarse para obtener el vector que se habría construido a partir de todo el cuerpo.

Parte de la pregunta consiste en determinar un límite de cuánto se puede construir en la memoria antes de que se produzcan las escrituras en el disco. ¿Python ofrece algún mecanismo para determinar la cantidad de RAM disponible?


De otro comentario, deduzco que tu cuerpo encaja en la memoria, y tienes algunos núcleos para solucionar el problema, así que intentaría esto:

  • Encuentra un método para tener tu corpus en la memoria. Esto podría ser una especie de disco RAM con sistema de archivos o una base de datos. No tengo idea, cuál es la mejor para ti.
  • Tenga un pequeño uso del controlador de la secuencia de comandos de shell y genere cada segundo otro proceso del siguiente, siempre que quede x memoria (o, si desea hacer las cosas un poco más complejas, y el ancho de banda de E / S al disco):

    • Recorrer el corpus y construir y escribir algunos vectores.
  • al final, puede recopilar y combinar todos los vectores, si es necesario (esta sería la parte reducida)


Divida el cuerpo de manera uniforme en tamaño entre trabajos paralelos (uno por núcleo): procese en paralelo, ignorando cualquier línea incompleta (o si no puede saber si está incompleto, ignore la primera y la última línea de cada proceso)

Esa es la parte del mapa.

Utilice un trabajo para fusionar los más de 20 conjuntos de vectores de cada uno de los trabajos anteriores: ese es el paso de reducción.

Puede perder información de las líneas 2 * N, donde N es el número de procesos paralelos, pero gana al no agregar lógica complicada para intentar capturar estas líneas para procesar.


Dos ideas:

  1. Usa matrices numpy para representar vectores. Son mucho más eficientes en la memoria, al costo que obligarán a que los elementos del vector sean del mismo tipo (todas las entradas o todas las dobles ...).

  2. Haz múltiples pases, cada uno con un conjunto diferente de vectores. Es decir, elija los primeros vectores 1M y haga solo los cálculos que los involucran (usted dijo que son independientes, así que supongo que esto es viable). Luego, otra pasada sobre todos los datos con segundos vectores 1M.

Parece que estás al borde de lo que puedes hacer con tu hardware. Le ayudaría si pudiera describir qué hardware (en su mayoría, RAM) está disponible para esta tarea. Si hay 100k vectores, cada uno de ellos con 1M ints, esto da ~ 370GB. Si el método de múltiples pases es viable y tiene una máquina con 16GB de RAM, entonces es alrededor de ~ 25 pases, debería ser fácil de paralelizar si tiene un clúster.


Es difícil de decir exactamente porque faltan algunos detalles, por ejemplo. ¿Es esta una caja dedicada? ¿El proceso se ejecuta en varias máquinas? ¿Cambia la memoria disponible?

En general, recomiendo no volver a implementar el trabajo del sistema operativo.

Tenga en cuenta que el siguiente párrafo no parece aplicarse ya que se lee todo el archivo cada vez: probé la implementación tres, le doy un caché de disco en buen estado y veo qué sucede. Con un montón de rendimiento de caché puede no ser tan malo como cabría esperar.

También querrá almacenar en caché cálculos costosos que serán necesarios pronto. En resumen, cuando se calcula una operación costosa que se puede usar nuevamente, la almacena en un diccionario (o quizás en un disco, memcached, etc.), y luego mira allí antes de volver a realizar el cálculo. Los documentos de Django tienen una buena introduction .


Muchos de los métodos discutidos por otros en esta página son muy útiles, y recomiendo que cualquier persona que necesite resolver este tipo de problema los mire.

Uno de los aspectos cruciales de este problema es decidir cuándo dejar de construir vectores (o lo que sea que esté construyendo) en la memoria y volcar cosas en el disco. Esto requiere una forma (pythonesque) de determinar cuánta memoria le queda a uno.

Resulta que el módulo psutil python solo hace el truco.

Por ejemplo, digamos que quiero tener un bucle while que agregue cosas a una cola para que otros procesos puedan lidiar hasta que mi RAM esté llena al 80%. El siguiente pseudocódigo hará el truco:

while (someCondition): if psutil.phymem_usage().percent > 80.0: dumpQueue(myQueue,somefile) else: addSomeStufftoQueue(myQueue,stuff)

De esta manera, puede tener un proceso de seguimiento del uso de la memoria y decidir que es hora de escribir en el disco y liberar algo de memoria del sistema (decidir qué vectores almacenar en caché es un problema aparte).

PD. Atrezzo a Sean por sugerir este módulo.


No mencionó ninguna de las dos formas, pero si no lo hace, debe usar NumPy matrices para sus listas en lugar de las listas nativas de Python, que deberían ayudar a acelerar las cosas y reducir el uso de la memoria, así como hacer las matemáticas que esté haciendo. Más rápido y más fácil.

Si está familiarizado con C / C ++, también puede buscar en Cython , que le permite escribir parte o la totalidad de su código en C, que es mucho más rápido que Python, y se integra bien con las matrices NumPy. Es posible que desee crear un profile su código para averiguar qué lugares están demorando más tiempo y escribir esas secciones en C.

Es difícil decir cuál será el mejor enfoque, pero, por supuesto, cualquier aceleración que pueda lograr en partes críticas ayudará. También tenga en cuenta que una vez que se agote la memoria RAM, su programa comenzará a ejecutarse en la memoria virtual del disco, lo que probablemente causará más actividad de E / S en el disco que el programa en sí, por lo que si le preocupa la E / S del disco, La mejor opción es probablemente asegurarse de que el lote de datos en el que está trabajando en la memoria no sea mucho mayor que la RAM disponible.


Piense en utilizar una solución de base de datos en memoria existente como Redis . El problema de cambiar a disco una vez que la RAM se ha ido y los trucos para modificar este proceso ya deberían estar en su lugar. Cliente Python también.

Además, esta solución podría escalar verticalmente sin mucho esfuerzo.


Un par de bibliotecas vienen a la mente que tal vez quieras evaluar:

  • joblib : facilita la computación en paralelo y proporciona un almacenamiento en caché transparente del disco de salida y una reevaluación perezosa.

  • mrjob : facilita la escritura de trabajos de transmisión de Hadoop en Amazon Elastic MapReduce o su propio clúster de Hadoop.


Utilice una base de datos. Ese problema parece lo suficientemente grande como para que la elección del idioma (Python, Perl, Java, etc.) no haga una diferencia. Si cada dimensión del vector es una columna en la tabla, agregar algunos índices probablemente sea una buena idea. En cualquier caso, esta es una gran cantidad de datos y no se procesará muy rápido.


Yo sugeriría hacerlo de esta manera:

1) Construye la tubería fácil que mencionaste

2) Construya sus vectores en la memoria y "vacíelos" en una base de datos. ( Redis y MongoDB son buenos candidatos)

3) Determine cuánta memoria consume este procedimiento y paralelice en consecuencia (o incluso mejor utilice un enfoque de mapa / reducción, o una cola de tareas distribuida como el celery )

Además de todos los consejos mencionados anteriormente (numPy, etc.)


pytables un vistazo a pytables . Una de las ventajas es que puede trabajar con grandes cantidades de datos, almacenados en el disco, como si estuvieran en la memoria.

edición: dado que el rendimiento de E / S será un cuello de botella (si no es EL cuello de botella), deberá considerar la tecnología SSD: alta E / S por segundo y prácticamente sin tiempos de búsqueda. El tamaño de su proyecto es perfecto para las "unidades" SSD asequibles de hoy.