2011-11-23 11 views
7

W języku Python mam strumień plików i chcę skopiować pewną jego część do StringIO. Chcę, żeby to było jak najszybciej, z minimalną ilością kopii.Szybkie przenoszenie danych z pliku do niektórych StringIO

Ale jeśli to zrobię:

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

Myślę 2 kopie zostało zrobione, nie? Jedna kopia na dane z pliku, druga kopia wewnątrz StringIO na wewnętrzny bufor. Czy mogę uniknąć jednej z kopii? Nie muszę tymczasowy data, więc myślę, że jeden egzemplarz powinien być na tyle

+0

Co zamierzasz zrobić z 'stream'? Przeczytaj to?? –

+0

Czy używasz Python 2.x lub 3.x? –

+0

@JohnMachin: Ja też chcę to przeczytać i zmodyfikować. Pytanie jest ogólne o Pythonie, jeśli istnieje różnica między 2.x a 3.x, proszę powiedz – zaharpopov

Odpowiedz

8

W skrócie: nie można uniknąć 2 kopii za pomocą StringIO.

Niektóre założenia:

  • Używasz cStringIO, inaczej byłoby głupie, aby zoptymalizować to dużo.
  • To szybkość, a nie wydajność pamięci, której szukasz. Jeśli nie, zobacz rozwiązanie Jakoba Bowyera, lub użyj wariantu używając file.read(SOME_BYTE_COUNT), jeśli twój plik jest binarny.
  • Powiedziałeś już o tym w komentarzach, ale dla kompletności: chcesz faktycznie edytować zawartość, a nie tylko ją wyświetlić.

Długa odpowiedź: Ponieważ ciągi python są niezmienne, a bufor StringIO nie jest kopią będą musiały być wykonane wcześniej lub później; w przeciwnym razie zmieniałbyś niezmienny obiekt! Dla tego, co chcesz być możliwe, obiekt StringIO musiałby mieć dedykowaną metodę, która byłaby czytana bezpośrednio z obiektu pliku podanego jako argument. Nie ma takiej metody.

Poza StringIO istnieją rozwiązania, które pozwalają uniknąć dodatkowej kopii. Przy mojej głowie, to odczytać plik bezpośrednio do modyfikowalny tablicy bajtów, bez dodatkowych kopia:

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

To może być kłopotliwe do pracy, w zależności od zastosowania, którą zamierzasz, ponieważ jest tablicą wartości od 0 do 255, a nie tablica znaków. Ale jest funkcjonalnie równoważny obiektowi StringIO, a używanie notacji wycinania powinno przydać się tam, gdzie chcesz, przy użyciu np.fromstring, , np.tofile. Możesz także potrzebować np.insert, np.delete i np.append.

Jestem pewien, że istnieją inne moduły, które będą robić podobne rzeczy.

timeit:

Ile to wszystko naprawdę materii? Więc, zobaczmy. Zrobiłem plik 100 MB, largefile.bin. Następnie czytam plik, używając obu metod i zmieniając pierwszy bajt.

 
$ 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 

W moim przypadku użycie StringIO jest o 50% wolniejsze niż użycie numpy.

Wreszcie, dla porównania, edytując plik bezpośrednio:

 
$ 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 

Tak, to jest prawie 4500 razy szybciej. Oczywiście bardzo zależy to od tego, co zrobisz z plikiem. Zmiana pierwszego bajtu jest mało reprezentatywna. Ale używając tej metody, masz przewagę nad pozostałymi dwoma, a ponieważ większość systemów operacyjnych ma dobre buforowanie dysków, prędkość może być również bardzo dobra.

(Jeśli nie masz uprawnień do edycji pliku i chcesz uniknąć kosztu wykonania kopii roboczej, istnieje kilka możliwych sposobów zwiększenia prędkości. Jeśli możesz wybrać system plików, Btrfs ma copy-on-write Operacja kopiowania plików - wykonanie aktu pobrania kopii pliku praktycznie natychmiastowego Ten sam efekt można uzyskać, korzystając z migawki dowolnego systemu plików.

+0

czy oznacza to bez numpy, czyli w stdlib? może bytearray dla tego samego efektu? – zaharpopov

+0

Nie o tym wiem, nie. Bytearray wydaje się nie akceptować obiektów plików jako argumentu. –

+0

który brzmi jak wstyd, więc jedyny sposób na szybkie odczytanie modyfikowalnego bufora z pliku to numpy :( – zaharpopov

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

jak to jest szybciej? dodatkowa kopia wykonana również – zaharpopov

+0

Plik może być binarny ... – mac

6

Nie, nie jest dodatkowa kopia wykonana. Bufor używany do przechowywania danych jest taki sam. Zarówno data jak i wewnętrzny atrybut dostępny za pomocą StringIO.getvalue() są różnymi nazwami dla tych samych danych.

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' 

Szybkie przejrzeć the source pokazuje, że cStringIO nie zrobić kopię na budowę albo, ale to robi kopię na wywołanie cStringIO.getvalue(), więc nie mogę powtórzyć powyższą demonstrację.

+0

Ponieważ zawartość 'data' jest niezmienna, a zawartość' stream' nie jest, dodatkowa kopia musi być wykonana zaraz po zmodyfikowaniu obiektu StringIO , jeśli nie wcześniej. Pozostaje pytanie. –

+0

To jest inne pytanie. Jeśli chcesz wiedzieć, jak działa StringIO, najlepiej jest przeczytać 'StringIO.py'. –

+0

@MichaelHoffman: dziękuję, ale szczególnie interesuje mnie ta kopia wykonana po modyfikacji. Wiem, że StringIO to robi, moim pytaniem jest, jak uniknąć id. Jak odczytać dane bezpośrednio z pliku do modyfikowalnego StringIO? – zaharpopov

2

Może to, co szukasz jest buffer/memoryview:

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

W ten sposób można uzyskać dostęp kawałek oryginalnych danych bez kopiowania go. Jednak musisz być zainteresowany dostępem do tych danych tylko w formacie zorientowanym na bajty, ponieważ zapewnia to protokół buforowy.

Możesz znaleźć więcej informacji w tym powiązanym question.

Edit: W tym blog post znalazłem przez Reddit, niektóre bardziej informacje są podane w odniesieniu do tego samego problemu:

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

Według autora bez dodatkowych kopia jest tworzona i dane mogą być modyfikowane od bytearray jest zmienny.

+0

Czy mogę go zmodyfikować w ten sposób? – zaharpopov

+0

To zależy od obiektu, do którego można uzyskać dostęp. W dokumentacji [memoryview] (http://docs.python.org/library/stdtypes.html#memoryview) znajduje się przykład, który zmienia wartość w obiekcie 'bytearray' (bez zmiany jego rozmiaru). Jednak w twoim przykładzie 'file.read' zwróci ciąg znaków inmutable stringinmutable, więc nie będziesz mógł tego zrobić na tym obiekcie. – jcollado

+0

Właśnie widziałem [this] (http: //eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-buffer-protocol-and-memoryviews /) w reddit i wydaje się, że rozwiązuje problem, aby przenieść dane do pliku 'bytearray' używając' file .readinto'. – jcollado

Powiązane problemy