2011-11-23 17 views
7

In Python ho un flusso di file e voglio copiare parte di esso in un StringIO. Voglio che questo sia il più veloce possibile, con una copia minima.i dati veloci si spostano da un file a un altro StringIO

Ma se faccio:

data = file.read(SIZE) 
stream = StringIO(data) 

Penso 2 copie è stato fatto, no? Una copia in dati da file, un'altra copia all'interno di StringIO nel buffer interno. Posso evitare una delle copie? Non ho bisogno di temporanea data, quindi penso che una copia dovrebbe essere abbastanza

+0

Cosa hai intenzione di fare con 'stream'? Leggilo?? –

+0

Stai usando Python 2.xo 3.x? –

+0

@JohnMachin: Voglio leggerlo e modificarlo anch'io. La domanda è generale su Python, se c'è differenza tra 2.xe 3.x per favore diciamo – zaharpopov

risposta

8

In breve: non è possibile evitare 2 copie utilizzando StringIO.

Alcune ipotesi:

  • Stai usando cStringIO, altrimenti sarebbe sciocco per ottimizzare questo molto.
  • È la velocità e non l'efficienza della memoria che stai cercando. In caso contrario, vedere la soluzione di Jakob Bowyer o utilizzare una variante utilizzando file.read(SOME_BYTE_COUNT) se il file è binario.
  • Lo hai già affermato nei commenti, ma per completezza: vuoi veramente modificare i contenuti, non solo visualizzarli.

Risposta lunga: Dal momento che le stringhe di Python sono immutabili e il buffer StringIO non è, una copia dovrà essere fatta prima o poi; altrimenti cambierai un oggetto immutabile! Per quello che vuoi essere possibile, l'oggetto StringIO dovrebbe avere un metodo dedicato che legge direttamente da un oggetto file dato come argomento. Non esiste un tale metodo.

Fuori da di StringIO, esistono soluzioni che evitano la copia aggiuntiva. Fuori della parte superiore della mia testa, questo leggerà un file direttamente in un array di byte modificabile, non copia extra:

import numpy as np 
a = np.fromfile("filename.ext", dtype="uint8") 

Può essere ingombrante con cui lavorare, a seconda dell'uso che si intende, dal momento che è una serie di valori da 0 a 255, non una matrice di caratteri. Ma è funzionalmente equivalente a un oggetto StringIO, e l'utilizzo di np.fromstring, np.tostring, np.tofile e la notazione dell'affettatura dovrebbe portarti dove vuoi. Potrebbe anche essere necessario np.insert, np.delete e np.append.

Sono sicuro che ci sono altri moduli che faranno cose simili.

timeit:

Quanto tutto questo davvero questione? Bene vediamo. Ho creato un file da 100 MB, largefile.bin. Quindi leggo il file usando entrambi i metodi e cambio il primo byte.

 
$ python -m timeit -s "import numpy as np" "a = np.fromfile('largefile.bin', 'uint8'); a[0] = 1" 
10 loops, best of 3: 132 msec per loop 
$ python -m timeit -s "from cStringIO import StringIO" "a = StringIO(); a.write(open('largefile.bin').read()); a.seek(0); a.write('1')" 
10 loops, best of 3: 203 msec per loop 

Quindi nel mio caso, l'uso di StringIO è del 50% più lento rispetto all'uso di numpy.

Infine, per il confronto, la modifica del file direttamente:

 
$ python -m timeit "a = open('largefile.bin', 'r+b'); a.seek(0); a.write('1')" 
10000 loops, best of 3: 29.5 usec per loop 

Quindi, è quasi 4500 volte più veloce. Ovviamente, è estremamente dipendente da ciò che farai con il file. La modifica del primo byte è difficilmente rappresentativa. Ma usando questo metodo, hai un vantaggio sugli altri due, e dato che la maggior parte dei SO ha un buon buffering dei dischi, la velocità potrebbe essere molto buona.

(Se non ti è permesso modificare il file e quindi vuoi evitare il costo di fare una copia funzionante, ci sono un paio di modi possibili per aumentare la velocità. Se puoi scegliere il filesystem, Btrfs ha un copy-on-write operazione di copia di file - rendendo l'atto di prendere una copia di un file praticamente istantaneo.Per lo stesso effetto si può ottenere usando uno snapshot LVM di qualsiasi filesystem.)

+0

ci sono mezzi senza intorpidire, vale a dire in stdlib? forse bytearray per lo stesso effetto? – zaharpopov

+0

Non che io sappia, no. Bytearray sembra non accettare gli oggetti file come argomento. –

+0

sembra un peccato, quindi l'unico modo per leggere il buffer modificabile dal file veloce è con numpy :( – zaharpopov

-1
stream = StringIO() 
for line in file: 
    stream.write(line + "\n") 
+1

come è più veloce? anche la copia aggiuntiva è – zaharpopov

+0

Il file potrebbe essere binario ... – mac

6

No, non c'è una copia in più fatta. Il buffer utilizzato per memorizzare i dati è lo stesso. Sia data sia l'attributo interno accessibile utilizzando StringIO.getvalue() sono nomi diversi per gli stessi dati.

Python 2.7 (r27:82500, Jul 30 2010, 07:39:35) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-48)] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import StringIO 
>>> data = open("/dev/zero").read(1024) 
>>> hex(id(data)) 
'0xea516f0' 
>>> stream = StringIO.StringIO(data) 
>>> hex(id(stream.getvalue())) 
'0xea516f0' 

Uno scremato veloce attraverso the source mostra che cStringIO non fa una copia sulla costruzione sia, ma lo fa fare una copia a chiamare cStringIO.getvalue(), quindi non posso ripetere la dimostrazione di cui sopra.

+0

Poiché il contenuto di 'data' è immutabile e il contenuto di' stream' non lo è, la copia extra è destinata ad essere eseguita non appena l'oggetto StringIO viene modificato , se non prima. La domanda rimane. –

+0

Questa è una domanda diversa. Se vuoi sapere come funziona StringIO, la cosa migliore da fare è leggere 'StringIO.py'. –

+0

@MichaelHoffman: grazie, ma sono particolarmente interessato a quella copia fatta al momento della modifica. So che StringIO lo fa, la mia domanda è come evitare l'id. Come leggere i dati direttamente dal file al StringIO modificabile? – zaharpopov

2

Forse quello che stai cercando è un buffer/memoryview:

>>> data = file.read(SIZE) 
>>> buf = buffer(data, 0, len(data)) 

questo modo è possibile accedere a una fetta dei dati originali senza copiarlo. Tuttavia, è necessario essere interessati ad accedere a quei dati solo in formato byte oriented poiché questo è ciò che fornisce il protocollo buffer.

È possibile trovare ulteriori informazioni in questo relativo question.

Edit: In questo blog post ho trovato attraverso reddit, qualche informazione in più viene dato per quanto riguarda lo stesso problema:

>>> f = open.(filename, 'rb') 
>>> data = bytearray(os.path.getsize(filename)) 
>>> f.readinto(data) 

Secondo l'autore non viene creata alcuna copia in più e dati possono essere modificati in quanto bytearray è mutevole.

+0

posso modificarlo in questo modo? – zaharpopov

+0

Dipende dall'oggetto a cui si accede. Nella documentazione [memoryview] (http://docs.python.org/library/stdtypes.html#memoryview) c'è un esempio che modifica un valore in un oggetto 'bytearray' (senza modificarne le dimensioni). Tuttavia, nel tuo esempio, 'file.read' restituirà un inmutable stringinmutable, quindi non sarai in grado di farlo su quell'oggetto. – jcollado

+0

Ho appena visto [questo] (http: //eli.thegreenplace.net/2011/11/28/less-copy-in-python-con-buffer-protocol-and-memoryviews /) in reddit e sembra risolvere il problema per ottenere i dati in un file 'bytearray' usando' file .readinto'. – jcollado

Problemi correlati