2010-09-26 16 views
6

Soy un principiante completo para Python o cualquier lenguaje de programación serio para el caso. Finalmente conseguí un código de prototipo para trabajar, pero creo que será demasiado lento.Optimización para encontrar y reemplazar archivos grandes en Python

Mi objetivo es encontrar y reemplazar algunos caracteres chinos en todos los archivos (son csv) en un directorio con números enteros según un archivo csv que tengo. Los archivos están bien numerados por año-mes, por ejemplo 2000-01.csv, y serán los únicos archivos en ese directorio.

Voy a estar recorriendo alrededor de 25 archivos que están en el vecindario de 500mb cada uno (y alrededor de un millón de líneas). El diccionario que utilizaré tendrá unos 300 elementos y cambiaré unicode (carácter chino) a enteros. Intenté con una ejecución de prueba y, asumiendo que todo se amplía linealmente (?), Parece que tomará aproximadamente una semana para que se ejecute.

Gracias de antemano. Aquí está mi código (no se rían!):

# -*- coding: utf-8 -*- 

import os, codecs 

dir = "C:/Users/Roy/Desktop/test/" 

Dict = {'hello' : 'good', 'world' : 'bad'} 

for dirs, subdirs, files in os.walk(dir): 
    for file in files: 
     inFile = codecs.open(dir + file, "r", "utf-8") 
     inFileStr = inFile.read() 
     inFile.close() 
     inFile = codecs.open(dir + file, "w", "utf-8") 
     for key in Dict: 
      inFileStr = inFileStr.replace(key, Dict[key]) 
     inFile.write(inFileStr) 
     inFile.close() 
+0

Es la convención de Python nombrar las variables de instancia con letras minúsculas. También reemplazaría la palabra 'Dict' con algo diferente al tipo, para evitar confusiones futuras. –

+0

¿Sus claves de diccionario consisten en exactamente 1 carácter chino cada una, o hay varios caracteres por clave? ¿Por qué quieres reemplazar los caracteres chinos con números enteros? –

+0

@John: Tengo otros 35 archivos que tienen esta información ya codificada con enteros, y haré mi análisis en Stata, que no lee unicode. Necesito leer varios caracteres a la vez, no solo 1. – rallen

Respuesta

13

En su código actual, está leyendo todo el archivo en la memoria a la vez. Como son archivos de 500Mb, eso significa cadenas de 500Mb. Y luego haces reemplazos repetidos de ellos, lo que significa que Python tiene que crear una nueva cadena de 500Mb con el primer reemplazo, luego destruir la primera cadena, luego crear una segunda cadena de 500MB para el segundo reemplazo, luego destruir la segunda cadena, etcétera. para cada reemplazo. Eso resulta ser una gran cantidad de copia de datos hacia adelante y hacia atrás, sin mencionar el uso de mucha memoria.

Si sabe que los reemplazos siempre se incluirán en una línea, puede leer el archivo línea por línea iterando sobre él. Python almacenará en búfer la lectura, lo que significa que estará bastante optimizado. Debería abrir un nuevo archivo, con un nuevo nombre, para escribir el nuevo archivo simultáneamente. Realice el reemplazo en cada línea por turno y escríbalo inmediatamente.Hacer esto reducirá en gran medida la cantidad de memoria utilizada y la cantidad de memoria copiada de ida y vuelta como lo hace los reemplazos:

for file in files: 
    fname = os.path.join(dir, file) 
    inFile = codecs.open(fname, "r", "utf-8") 
    outFile = codecs.open(fname + ".new", "w", "utf-8") 
    for line in inFile: 
     newline = do_replacements_on(line) 
     outFile.write(newline) 
    inFile.close() 
    outFile.close() 
    os.rename(fname + ".new", fname) 

Si no puede estar seguro de si siempre estarán en una línea , las cosas se ponen un poco más difíciles; Tendría que leer en bloques manualmente, usando inFile.read(blocksize), y mantener un seguimiento cuidadoso de si podría haber una coincidencia parcial al final del bloque. No es tan fácil de hacer, pero por lo general todavía vale la pena para evitar las cadenas de 500Mb.

Otra gran mejora sería si pudieras hacer los reemplazos de una vez, en lugar de intentar un montón de reemplazos en orden. Hay varias maneras de hacerlo, pero que se adapta mejor depende completamente de lo que está reemplazando y con qué. Para traducir caracteres individuales en otra cosa, el método translate de objetos Unicode puede ser conveniente. Se le pasa un mapeo dict puntos de código Unicode (como enteros) para cadenas Unicode:

>>> u"\xff and \ubd23".translate({0xff: u"255", 0xbd23: u"something else"}) 
u'255 and something else' 

Para sustitución de subcadenas (y no sólo caracteres individuales), se puede utilizar el módulo re. La función re.sub (y el método de las expresiones regulares compiladas sub) pueden tomar un invocable (una función) como primer argumento, que luego será llamado para cada partido:

>>> import re 
>>> d = {u'spam': u'spam, ham, spam and eggs', u'eggs': u'saussages'} 
>>> p = re.compile("|".join(re.escape(k) for k in d)) 
>>> def repl(m): 
...  return d[m.group(0)] 
... 
>>> p.sub(repl, u"spam, vikings, eggs and vikings") 
u'spam, ham, spam and eggs, vikings, saussages and vikings' 
+0

Me había olvidado de la cadena no mutable.Mucho mejor que mi respuesta. – aaronasterling

+2

Iba a agregar a su respuesta que la cadena de 500Mb no es solo una cuestión de ajuste en la RAM o empujar al intercambio, sino también de cómo la mayoría de las arquitecturas funcionan mejor con operaciones repetidas en un conjunto de datos más pequeño (algo que encaja en la CPU guarda bien, aunque Python rápidamente llena el caché con sus propias cosas.) Además de eso, Python también optimiza las asignaciones de objetos más pequeños que de los grandes, lo cual es particularmente importante en Windows (pero todas las plataformas se benefician de ello con algunos grado). –

+0

La localización de los archivos de salida en un disco físico diferente probablemente hará que el procedimiento general sea más rápido, ya que el cuello de botella estará leyendo y escribiendo en el disco. Probablemente puedas mejorar aún más el rendimiento haciendo las escrituras en un hilo separado y pasando cada línea a él a través de un 'Queue.Queue'. Creo que la utilidad de esta última medida dependerá de la efectividad de la memoria caché readahead de la unidad de lectura en combinación con cualquier caché de escritura en la unidad de escritura. Pero eso también es quizás demasiado pesado para un principiante de Python. – intuited

0

abrir los archivos de lectura/escritura ('r +') y evitar la doble cerrar (y probablemente asociado al ras buffer) de apertura /. Además, si es posible, no reescriba el archivo completo, busque y vuelva a escribir solo las áreas modificadas después de reemplazar el contenido del archivo. Lea, reemplace, escriba las áreas modificadas (si las hay).

Eso todavía no ayudará al rendimiento demasiado sin embargo, me gustaría perfilar y determinar dónde está realmente el rendimiento alcanzado y luego pasar a la optimización. Podría ser solo que la lectura de los datos del disco es muy lenta, y no hay mucho que puedas hacer al respecto en Python.

+1

'rw' no es 'leer/escribir'. Es solo 'leer', ya que la 'w' se ignora por completo. Los modos para 'leer/escribir' son 'r +', 'w +' y 'a +', donde cada uno hace algo sutilmente diferente. Volver a escribir un archivo mientras lo lee es complicado, ya que necesita buscar entre lecturas y escrituras, y debe tener cuidado de no sobrescribir lo que no ha leído todavía. –

+0

@Thomas: Ah, sí. Siempre queda atrapado en las banderas abiertas(). Demasiado C :). De todos modos, mi sugerencia fue que primero leyeras el archivo y luego solo escribas los cambios, no para escribir los cambios mientras los lees. –

+1

La cadena que pasa para abrir() es en realidad lo que pasaría a 'fopen()' en C (y por qué tiene una semántica tan sucky) así que "demasiada C" no es una excusa :-) –

1

Un par de cosas (no relacionada con el problema de optimización):

dir + file debe ser os.path.join(dir, file)

Es posible que desee volver a utilizar no archivoentrada, pero en lugar abierto (y escribir) un archivo de salida independiente. Esto tampoco aumentará el rendimiento, pero es una buena práctica.

No sé si está vinculado a E/S o CPU, pero si la utilización de su CPU es muy alta, es posible que desee utilizar el enhebrado, con cada hilo operando en un archivo diferente (por lo que con un quad procesador central, estarías leyendo/escribiendo 4 archivos diferentes simultáneamente).

+0

Tiene los consejos de enhebrado completamente al revés. En Python, conéctese para sortear los límites de IO. Esto se debe al bloqueo de intérprete global. Utiliza subprocesos para aplicaciones limitadas de CPU/memoria, que es lo que es. (solo 50 operaciones de IO en una semana;) – aaronasterling

+0

Buen punto. Sabía sobre el bloqueo global, pero en realidad no pensé en los subprocesos frente a los subprocesos. Aprendiendo algo nuevo cada día. – babbitt

+0

@ AaronMcSmooth: Esperaría que esto esté vinculado a E/S, ya que buscar una cadena y reemplazarla de un diccionario es un esfuerzo bajo para un procesador moderno. Pero en este caso, el multihilo no ayudará a menos que algunos de los archivos estén en discos físicos separados o que sea posible ubicar los archivos traducidos en un disco físico diferente. – intuited

2

Creo que se puede reducir el uso de memoria en gran medida (y así limitar el uso del intercambio y hacer las cosas más rápido) leyendo una línea a la vez y escribiéndola (después de los reemplazos de expresiones regulares ya sugeridas) en un archivo temporal, luego moviendo el archivo para reemplazar el original.

Cuestiones relacionadas