2013-05-21 8 views
25

Ho un numero di file di testo molto grandi che devo elaborare, il più grande dei quali è di circa 60 GB.Processo molto grande (> 20 GB) file di testo riga per riga

Ogni riga ha 54 caratteri in sette campi e desidero rimuovere gli ultimi tre caratteri da ciascuno dei primi tre campi, il che dovrebbe ridurre le dimensioni del file di circa il 20%.

Sono nuovo di zecca per Python e ho un codice che farà quello che voglio fare a circa 3,4 GB all'ora, ma per essere un esercizio utile ho davvero bisogno di ottenere almeno 10 GB/ora - è lì un modo per velocizzare questo? Questo codice non si avvicina a sfidare il mio processore, quindi sto facendo un'ipotesi non istruita che è limitata dalla velocità di lettura e scrittura sul disco rigido interno?

ProcessLargeTextFile(): 
    r = open("filepath", "r") 
    w = open("filepath", "w") 
    l = r.readline() 
    while l: 
     x = l.split(' ')[0] 
     y = l.split(' ')[1] 
     z = l.split(' ')[2] 
     w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])) 
     l = r.readline() 
r.close() 
w.close() 

Qualsiasi aiuto sarebbe molto apprezzato. Sto usando la GUI Python IDLE su Windows 7 e ho 16 GB di memoria - forse un sistema operativo diverso sarebbe più efficiente?

Modifica: Ecco un estratto del file da elaborare.

70700.642014 31207.277115 -0.054123 -1585 255 255 255 
70512.301468 31227.990799 -0.255600 -1655 155 158 158 
70515.727097 31223.828659 -0.066727 -1734 191 187 180 
70566.756699 31217.065598 -0.205673 -1727 254 255 255 
70566.695938 31218.030807 -0.047928 -1689 249 251 249 
70536.117874 31227.837662 -0.033096 -1548 251 252 252 
70536.773270 31212.970322 -0.115891 -1434 155 158 163 
70533.530777 31215.270828 -0.154770 -1550 148 152 156 
70533.555923 31215.341599 -0.138809 -1480 150 154 158 
+0

Se stai scrivendo in Python 2.7, puoi provare a girare su [PyPy] (http://pypy.org/). Il compilatore just-in-time potrebbe darti delle prestazioni in accelerazione sul campo, anche se non sono sicuro di quanto sarebbe di aiuto se il filesystem fosse il collo di bottiglia. – pcurry

+0

puoi darci un piccolo snippet del file? – root

risposta

0

Puoi provare a salvare il risultato di divisione prima di farlo e non farlo ogni volta che ti serve un campo. Potrebbe essere questo accelera.

puoi anche provare a non eseguirlo in gui. Eseguilo in cmd.

1

Leggere il file utilizzando for l in r: per beneficiare del buffering.

20

E 'più idiomatico per scrivere il codice come questo

def ProcessLargeTextFile(): 
    with open("filepath", "r") as r, open("outfilepath", "w") as w: 
     for line in r: 
      x, y, z = line.split(' ')[:3] 
      w.write(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])) 

Il risparmio principale è quello di fare solo il split una volta, ma se la CPU non viene tassato, questo rischia di rendere molto poca differenza

può contribuire a risparmiare poche migliaia di righe alla volta e scriverle in un solo colpo per ridurre il thrashing del disco rigido. Un milione di righe è solo 54 MB di RAM!

def ProcessLargeTextFile(): 
    bunchsize = 1000000  # Experiment with different sizes 
    bunch = [] 
    with open("filepath", "r") as r, open("outfilepath", "w") as w: 
     for line in r: 
      x, y, z = line.split(' ')[:3] 
      bunch.append(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])) 
      if len(bunch) == bunchsize: 
       w.writelines(bunch) 
       bunch = [] 
     w.writelines(bunch) 

suggerite da @Janne, un modo alternativo per generare le linee

def ProcessLargeTextFile(): 
    bunchsize = 1000000  # Experiment with different sizes 
    bunch = [] 
    with open("filepath", "r") as r, open("outfilepath", "w") as w: 
     for line in r: 
      x, y, z, rest = line.split(' ', 3) 
      bunch.append(' '.join((x[:-3], y[:-3], z[:-3], rest))) 
      if len(bunch) == bunchsize: 
       w.writelines(bunch) 
       bunch = [] 
     w.writelines(bunch) 
+0

se le linee sono di dimensioni costanti, si potrebbe provare a leggere/scrivere il file in blocchi più grandi ... – root

+0

@root Non dovrebbe la roba 'for' fare il buffering in quel (e altri) casi (s)? – glglgl

+0

@glglgl - beh, potrebbe rendere possibile eseguire le operazioni di sostituzione su migliaia di linee al momento ... (non è sicuro quale sia il modo più veloce - forse un regex?) – root

3

Coloro sembrare file molto grandi ... Perché sono così grandi? Che elaborazione stai facendo per linea? Perché non utilizzare un database con alcune mappe per ridurre le chiamate (se appropriato) o semplici operazioni dei dati? Il punto di un database è quello di astrarre la gestione e la gestione di grandi quantità di dati che non tutti possono rientrare nella memoria.

È possibile iniziare a giocare con l'idea con sqlite3 che utilizza solo file flat come database. Se trovi l'idea utile, aggiorna a qualcosa di un po 'più robusto e versatile come postgresql.

Creare un database

conn = sqlite3.connect('pts.db') 
c = conn.cursor() 

crea una tabella

c.execute('''CREATE TABLE ptsdata (filename, line, x, y, z''') 

quindi utilizzare uno degli algoritmi di cui sopra per inserire tutte le linee e punti nel database chiamando

c.execute("INSERT INTO ptsdata VALUES (filename, lineNumber, x, y, z)") 

Ora come lo usi dipende da cosa vuoi fare. Ad esempio, per lavorare con tutti i punti in un file facendo una query

c.execute("SELECT lineNumber, x, y, z FROM ptsdata WHERE filename=file.txt ORDER BY lineNumber ASC") 

E ottenere n righe alla volta da questa query con

c.fetchmany(size=n) 

Sono sicuro che c'è un wrapper per il meglio le istruzioni SQL da qualche parte, ma tu hai l'idea.

+0

Grazie Chris, i file sono file .PTS per informazioni sulla nuvola di punti. Ogni riga rappresenta un punto diverso nello spazio in coordinate cartesiane e questo è il formato che otteniamo i dati dal fornitore e ciò che il nostro software richiede. –

+0

Nello spazio 3D? L'ordine dei dati è importante? E come usa il software i dati? – craastad

+2

@ChrisRaastad: Tom_b ha chiesto aiuto per il refactoring del sistema utilizzato o per il miglioramento del codice fornito? –

3

Il tuo codice è piuttosto poco idiomatico e fa molte più chiamate di funzioni del necessario. Una versione più semplice è:

ProcessLargeTextFile(): 
    with open("filepath") as r, open("output") as w: 
     for line in r: 
      fields = line.split(' ') 
      fields[0:2] = [fields[0][:-3], 
          fields[1][:-3], 
          fields[2][:-3]] 
      w.write(' '.join(fields)) 

e non so di un filesystem moderno che è più lento rispetto a Windows. Poiché sembra che tu stia utilizzando questi enormi file di dati come database, hai considerato l'utilizzo di un vero database?

Infine, se sei solo interessato a ridurre le dimensioni del file, hai considerato di comprimere/zippare i file?

3
ProcessLargeTextFile(): 
    r = open("filepath", "r") 
    w = open("filepath", "w") 
    l = r.readline() 
    while l: 

Come già suggerito, è possibile utilizzare un ciclo for per renderlo più ottimale.

x = l.split(' ')[0] 
    y = l.split(' ')[1] 
    z = l.split(' ')[2] 

Si sta eseguendo un'operazione di scissione 3 volte qui, a seconda della dimensione di ogni riga questo avrà un impatto sulle prestazioni detremental. Dovresti dividere una volta e assegnare x, y, z alle voci nella matrice che ritorna.

w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])) 

Ogni riga che stai leggendo, stai scrivendo immediatamente sul file, che è molto intensivo di I/O. È consigliabile prendere in considerazione il buffering dell'output in memoria e premere periodicamente sul disco. Qualcosa di simile a questo:

BUFFER_SIZE_LINES = 1024 # Maximum number of lines to buffer in memory 

def ProcessLargeTextFile(): 
    r = open("filepath", "r") 
    w = open("filepath", "w") 
    buf = "" 
    bufLines = 0 
    for lineIn in r: 

     x, y, z = lineIn.split(' ')[:3] 
     lineOut = lineIn.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]) 
     bufLines+=1 

     if bufLines >= BUFFER_SIZE: 
      # Flush buffer to disk 
      w.write(buf) 
      buf = "" 
      bufLines=1 

     buf += lineOut + "\n" 

    # Flush remaining buffer to disk 
    w.write(buf) 
    buf.close() 
    r.close() 
    w.close() 

è possibile modificare BUFFER_SIZE per determinare un equilibrio ottimale tra l'utilizzo di memoria e la velocità.

12

Misura! Hai qualche suggerimento utile su come migliorare il tuo codice Python e sono d'accordo con loro. Ma dovresti prima capire, qual è il tuo vero problema. I miei primi passi per trovare il collo di bottiglia sarebbero:

  • Rimuovere qualsiasi elaborazione dal codice. Basta leggere e scrivere i dati e misurare la velocità. Se solo leggere e scrivere i file è troppo lento, non è un problema del tuo codice.
  • Se solo la lettura e la scrittura sono già lente, provare a utilizzare più dischi. Stai leggendo e scrivendo allo stesso tempo. Sullo stesso disco? Se sì, prova ad usare dischi diversi e riprova.
  • Potrebbe essere utile anche una libreria io asincrona (Twisted?).

Se hai trovato il problema esatto, chiedi di nuovo per l'ottimizzazione di quel problema.

5

Dato che non sembra essere limitato dalla CPU, ma piuttosto dall'I/O, hai provato con alcune variazioni sul terzo parametro di open?

In effetti, questo terzo parametro può essere utilizzato per fornire la dimensione del buffer da utilizzare per le operazioni sui file!

La scrittura semplice open("filepath", "r", 16777216) utilizzerà i buffer da 16 MB durante la lettura dal file. Deve aiutare

Utilizzare lo stesso per il file di output e misurare/confrontare con il file identico per il resto.

Nota: questo è lo stesso tipo di ottimizzazione suggerito da altri, ma è possibile ottenerlo qui gratuitamente, senza modificare il codice, senza doverlo tamponare.

2

Poiché si menziona solo il risparmio di spazio come vantaggio, c'è qualche ragione per cui non è possibile archiviare i file gzip? Ciò dovrebbe salvare il 70% e oltre su questi dati. Oppure prendere in considerazione l'idea di ottenere NTFS per comprimere i file se l'accesso casuale è ancora importante. Otterrai risparmi molto più drastici sui tempi di I/O dopo uno di questi.

Ancora più importante, dove sono i dati che stai ottenendo solo 3,4 GB/ora? Questo è giù intorno alle velocità USBv1.

5

aggiungerò questa risposta per spiegare il motivo per cui buffer ha un senso e offrono anche una soluzione più

Hai trovato incredibilmente cattive prestazioni. Questo articolo Is it possible to speed-up python IO? mostra che una lettura da 10 gb dovrebbe durare circa 3 minuti. La scrittura sequenziale ha la stessa velocità. Quindi ti manca un fattore di 30 e il tuo obiettivo di rendimento è ancora 10 volte più lento di quello che dovrebbe essere possibile.

Quasi certamente questo tipo di disparità si trova in il numero di head seeks che il disco sta facendo. Un colpo alla testa richiede millisecondi. Una ricerca singola corrisponde a diversi megabyte di lettura/scrittura sequenziale. Enormemente costoso. Le operazioni di copia sullo stesso disco richiedono la ricerca tra input e output. Come è stato affermato, un modo per ridurre le ricerche è di bufferizzare in modo tale che molti megabyte vengono letti prima di scrivere su disco e viceversa. Se riesci a convincere il sistema python io a farlo, bene. Altrimenti puoi leggere ed elaborare linee in un array di stringhe e poi scrivere dopo che sono già pronti 50 mb di output. Questa dimensione indica che una ricerca indurrà un colpo di prestazioni del 10% < rispetto al trasferimento dei dati stesso.

L'altro modo molto semplice per eliminare le ricerche tra i file di input e di output è utilizzare una macchina con due dischi fisici e canali io completamente separati per ciascuno. Input da uno. Uscita ad altro. Se stai facendo molte grandi trasformazioni di file, è bene avere una macchina con questa funzionalità.

Problemi correlati