2011-11-23 5 views
7

En Python tengo una secuencia de archivos, y quiero copiar una parte de ella en un StringIO. Quiero que esto sea lo más rápido posible, con una copia mínima.datos rápidos mover del archivo a algunos StringIO

Pero si lo hago:

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

Pienso 2 copias fue hecho, no? Una copia en los datos del archivo, otra copia dentro de StringIO en el búfer interno. ¿Puedo evitar una de las copias? No necesito temporal data, así que creo que una copia debe ser lo suficientemente

+0

¿Qué vas a hacer con 'stream'? Léelo? –

+0

¿Está utilizando Python 2.xo 3.x? –

+0

@JohnMachin: Quiero leer y modificarlo también. La pregunta es general sobre Python, si hay una diferencia entre 2.x y 3.x, por favor diga – zaharpopov

Respuesta

8

En resumen: no puede evitar 2 copias con StringIO.

Algunos supuestos:

  • Estás usando cStringIO, de lo contrario sería tonto para optimizar tanto.
  • Es la velocidad y no la eficiencia de la memoria lo que buscas. De lo contrario, consulte la solución de Jakob Bowyer, o use una variante usando file.read(SOME_BYTE_COUNT) si su archivo es binario.
  • Ya lo ha expresado en los comentarios, pero para completarlo: quiere editar realmente los contenidos, no solo verlos.

Respuesta larga: Como las cadenas de Python son inmutables y la memoria intermedia StringIO no es, una copia tendrá que ser realizado antes o después; de lo contrario estarías alterando un objeto inmutable! Para lo que quiere ser posible, el objeto StringIO necesitaría tener un método dedicado que lea directamente de un objeto de archivo dado como argumento. No hay tal método.

Fuera de de StringIO, hay soluciones que evitan la copia adicional. De la parte superior de mi cabeza, esto va a leer un archivo directamente en una matriz de bytes modificable, sin copia extra:

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

Puede ser engorroso para trabajar, dependiendo del uso que usted tiene la intención, ya que es un conjunto de valores de 0 a 255, no una matriz de caracteres. Pero es funcionalmente equivalente a un objeto StringIO, y usando np.fromstring, np.tostring, np.tofile y la notación de corte debería llevarlo a donde lo desee. Es posible que también necesite np.insert, np.delete y np.append.

Estoy seguro de que hay otros módulos que harán cosas similares.

TimeIt:

¿Cuánto cuesta todo esto realmente importa ? Bien, veamos. He hecho un archivo de 100MB, largefile.bin. Luego leo en el archivo usando ambos métodos y cambio el primer 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 

Así que, en mi caso, utilizar StringIO es un 50% más lento que usar numpy.

Por último, para la comparación, editar el archivo directamente:

 
$ 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 

Por lo tanto, es casi 4.500 veces más rápido. Por supuesto, es extremadamente dependiente de lo que vas a hacer con el archivo. Alterar el primer byte es poco representativo. Pero al usar este método, tienes una ventaja sobre los otros dos, y como la mayoría de los SO tienen un buen almacenamiento de memoria en los discos, la velocidad también puede ser muy buena.

(Si no puede editar el archivo y quiere evitar el costo de hacer una copia de trabajo, hay un par de maneras posibles de aumentar la velocidad. Si puede elegir el sistema de archivos, Btrfs tiene una copy-on-write operación de copia de archivos: tomar el acto de tomar una copia de un archivo prácticamente al instante. Se puede lograr el mismo efecto usando una instantánea LVM de cualquier sistema de archivos)

+0

¿hay medios sin numpy, es decir, en stdlib? ¿tal vez bytearray para el mismo efecto? – zaharpopov

+0

No es que yo sepa, no. Bytearray parece no aceptar objetos de archivo como argumento. –

+0

que suena como una vergüenza, así que la única manera de leer rápidamente el búfer modificable del archivo es con numpy :( – zaharpopov

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

¿cómo es esto más rápido? copia extra también realizada – zaharpopov

+0

Aunque el archivo podría ser binario ... – mac

6

No, no es una copia extra hecho. El buffer utilizado para almacenar los datos es el mismo. Tanto data como el atributo interno accesible usando StringIO.getvalue() son nombres diferentes para los mismos datos.

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' 

Un descremada rápida a través de the source muestra que cStringIO no hace una copia de la construcción tampoco, pero sí lo hace una copia en llamar cStringIO.getvalue(), por lo que no se puede repetir la demostración anterior.

+0

Dado que el contenido de 'data' es inmutable y el contenido de' stream' no lo es, la copia adicional debe realizarse tan pronto como se modifique el objeto StringIO si no antes La pregunta permanece –

+0

Esa es una pregunta diferente. Si desea saber cómo funciona StringIO, lo mejor que puede hacer es leer 'StringIO.py'. –

+0

@MichaelHoffman: gracias, pero estoy específicamente interesado en la copia realizada cuando se realiza la modificación. Sé que StringIO lo hace, mi pregunta es cómo evitar la identificación. ¿Cómo se leen los datos directamente desde el archivo a StringIO modificable? – zaharpopov

2

Tal vez lo que estás buscando es un buffer/memoryview:

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

esta manera se puede acceder a una porción de los datos originales sin copiarlo. Sin embargo, debe estar interesado en acceder a esos datos solo en formato orientado a bytes, ya que eso es lo que proporciona el protocolo de búfer.

Puede encontrar más información en este tema relacionado question.

Editar: En este blog post He encontrado a través de Reddit, algo más de información se da en relación con el mismo problema:

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

Según el autor no se crea una copia adicional y los datos pueden ser modificados desde bytearray es mutable.

+0

¿puedo modificarlo así? – zaharpopov

+0

Depende del objeto al que se accede. En la documentación [memoryview] (http://docs.python.org/library/stdtypes.html#memoryview) hay un ejemplo que cambia un valor en un objeto 'bytearray' (sin cambiar su tamaño). Sin embargo, en su ejemplo, 'file.read' devolverá una cadena inmutable enmutable, por lo que no podrá hacer eso en ese objeto. – jcollado

+0

Acabo de ver [esto] (http: //eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews /) en reddit y parece resolver el problema para obtener los datos en un 'bytearray' usando' file .readinto'. – jcollado

Cuestiones relacionadas