2010-09-26 11 views
6

Sono un principiante completo in Python o qualsiasi linguaggio di programmazione serio per quella materia. Alla fine ho ottenuto un codice prototipo per funzionare, ma penso che sarà troppo lento.Ottimizzazione di ricerca e sostituzione su file di grandi dimensioni in Python

Il mio obiettivo è trovare e sostituire alcuni caratteri cinesi su tutti i file (sono csv) in una directory con numeri interi come per un file csv che ho. I file sono ben numerati per anno-mese, ad esempio 2000-01.csv, e saranno gli unici file in quella directory.

Sarò in loop su circa 25 file che si trovano nelle vicinanze di 500 MB ciascuno (e circa un milione di righe). Il dizionario che userò avrà circa 300 elementi e cambierò unicode (carattere cinese) in interi. Ho provato con un test e, supponendo che tutto si riduca linearmente (?), Sembra che ci vorrebbe circa una settimana per farlo funzionare.

Grazie in anticipo. Ecco il mio codice (non ridete!):

# -*- 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

È la convenzione di Python per denominare le variabili di istanza con lettere minuscole. Sostituirei anche la parola 'Dict' con qualcosa di diverso dal tipo, per evitare confusione futura. –

+0

Le chiavi del dizionario sono costituite da esattamente 1 carattere cinese ciascuna o sono possibili più caratteri per chiave? Perché vuoi sostituire i caratteri cinesi con numeri interi? –

+0

@John: Ho altri 35 file con queste informazioni già codificate con numeri interi, e farò la mia analisi su Stata, che non legge l'unicode. Ho bisogno di leggere più caratteri alla volta, non solo 1. – rallen

risposta

13

Nel codice corrente, stai leggendo l'intero file in memoria in una sola volta. Dal momento che sono file da 500 Mb, ciò significa stringhe da 500 Mb. E poi ne fai ripetute sostituzioni, il che significa che Python deve creare una nuova stringa da 500 Mb con la prima sostituzione, quindi distruggere la prima stringa, quindi creare una seconda stringa da 500 Mb per la seconda sostituzione, quindi distruggere la seconda stringa, eccetera, per ogni sostituzione. Questo risulta essere un bel po 'di copia dei dati avanti e indietro, per non parlare dell'uso di molta memoria.

Se si sa che le sostituzioni saranno sempre contenute in una riga, è possibile leggere il file riga per riga ripetendo su di esso. Python bufferizzerà la lettura, il che significa che sarà abbastanza ottimizzato. Dovresti aprire un nuovo file, con un nuovo nome, per scrivere contemporaneamente il nuovo file. Eseguire a turno la sostituzione su ciascuna riga e scriverla immediatamente.In questo modo si riduce notevolmente la quantità di memoria utilizzata e la quantità di memoria copiati avanti e indietro come si fa le sostituzioni:

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) 

Se non si può essere certi se sarò sempre in una riga le cose diventano un po 'più difficili; dovresti leggere i blocchi manualmente, usando inFile.read(blocksize), e tenere traccia attentamente se ci potrebbe essere una corrispondenza parziale alla fine del blocco. Non è facile da fare, ma in genere vale comunque la pena di evitare le stringhe da 500 Mb.

Un altro grande miglioramento sarebbe se si potesse fare le sostituzioni in un colpo solo, piuttosto che provare un intero gruppo di sostituzioni in ordine. Ci sono diversi modi per farlo, ma quello che si adatta meglio dipende interamente da ciò che stai sostituendo e con cosa. Per tradurre singoli caratteri in qualcos'altro, il metodo translate di oggetti Unicode può essere conveniente. Si passa una mappatura dict codepoints Unicode (come numeri interi) per le stringhe Unicode:

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

Per sostituzione di sottostringhe (e non solo singoli caratteri), è possibile utilizzare il modulo re. La funzione re.sub (e il metodo di espressioni regolari compilate sub) può prendere un callable (una funzione) come primo argomento, che verrà poi chiamata per ogni partita:

>>> 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

Mi ero dimenticato della stringa non mutabile.Molto più bello della mia risposta. – aaronasterling

+2

Stavo per aggiungere alla tua risposta che la stringa 500Mb non è solo una questione di inserimento in RAM o di push in swap, ma anche di come la maggior parte delle architetture si comporta meglio con operazioni ripetute su un set di dati più piccolo (qualcosa che si adatta la CPU lavora bene nella cache, anche se Python riempie rapidamente la cache con le sue cose.) Oltre a ciò, Python ottimizza anche le allocazioni di oggetti più piccoli più di quelli grandi, il che importa in particolare su Windows (ma tutte le piattaforme ne traggono vantaggio ad alcuni grado.) –

+0

Individuare i file di output su un disco fisico diverso probabilmente renderà la procedura generale più veloce, dal momento che il collo di bottiglia sarà in lettura e scrittura su disco. Probabilmente potresti migliorare ulteriormente le prestazioni eseguendo le scritture in un thread separato e passando ogni riga ad esso attraverso un 'Queue.Queue'. Penso che l'utilità di quest'ultima misura dipenda dall'efficacia della cache readahead dell'unità di lettura in combinazione con qualsiasi cache di scrittura sull'unità di scrittura. Ma forse è anche un po 'troppo pesante per un principiante Python. – intuited

0

Aprire i file di lettura/scrittura ('r +') ed evitare la doppia apertura/chiusura (e probabilmente associato buffer di incasso). Inoltre, se possibile, non scrivere nuovamente l'intero file, cercare e riscrivere solo le aree modificate dopo aver effettuato la sostituzione sul contenuto del file. Leggere, sostituire, scrivere aree modificate (se presenti).

Ciò non aiuterà ancora le prestazioni troppo molto però: vorrei definire e determinare dove si trova effettivamente il riscontro e quindi passare all'ottimizzazione. Potrebbe essere solo la lettura dei dati da disco che è molto lenta, e non c'è molto che tu possa fare a riguardo in Python.

+1

'rw' non è 'lettura/scrittura'. È solo "letto", poiché la "w" è completamente ignorata. Le modalità per "leggi/scrivi" sono "r +", "w +" e "a +", dove ognuno fa qualcosa di leggermente diverso. Riscrivere un file mentre lo leggi è difficile, poiché devi cercare tra le letture e le scritture e devi stare attento a non sovrascrivere ciò che non hai ancora letto. –

+0

@Thomas: Ah, sì. Rimani sempre sorpreso dalle bandiere open(). Troppa C :). Ad ogni modo, il mio suggerimento era di leggere il file completamente prima e poi solo di riscrivere le modifiche, non di riscrivere i cambiamenti durante la lettura. –

+1

La stringa che si passa a open() è in realtà ciò che si passa a 'fopen()' in C (e perché ha una tale semantica sucky), quindi "troppa C" non è una scusa :-) –

1

Un paio di cose (non correlati al problema di ottimizzazione):

dir + file dovrebbe essere os.path.join(dir, file)

Si potrebbe desiderare di non riutilizzare infile, ma invece aperta (e scrivere) un file di output separato. Anche questo non aumenterà le prestazioni, ma è una buona pratica.

Non so se sei legato I/O o cpu, ma se l'utilizzo della cpu è molto alto, potresti voler usare il threading, con ogni thread che funziona su un file diverso (quindi con un quad core processor, stareste leggendo/scrivendo 4 file diversi simultaneamente).

+0

Hai il consiglio di filettatura completamente all'indietro. In Python, thread per aggirare i limiti di I/O. Ciò è dovuto al Global Interpreter Lock. Si utilizzano sottoprocessi per le applicazioni con CPU/memoria che è ciò che è. (solo 50 operazioni IO in una settimana;) – aaronasterling

+0

Buon punto. Sapevo del blocco globale, ma in realtà non pensavo ai sottoprocessi e ai thread. Imparare qualcosa di nuovo ogni giorno. – babbitt

+0

@AaronMcSmooth: Mi aspetterei che questo sia legato all'I/O, dal momento che cercare una stringa e sostituirla da un dizionario è uno sforzo piuttosto basso per un processore moderno. Ma in questo caso il multithreading non sarà di aiuto a meno che alcuni file non siano su dischi fisici separati o sia possibile localizzare i file tradotti su un disco fisico diverso. – intuited

2

penso che si può abbassare l'utilizzo della memoria notevolmente (e quindi limitare l'uso dello swap e rendere le cose più veloci) leggendo una riga alla volta e scrivendola (dopo le sostituzioni di espressioni regolari già suggerite) in un file temporaneo, quindi spostando il file per sostituire l'originale.

Problemi correlati