2013-07-17 14 views
7

Potrzebuję przetworzyć dane, które są kilkaset razy większe niż pamięć RAM. Chciałbym przeczytać duży fragment, przetworzyć go, zapisać wynik, uwolnić pamięć i powtórzyć. Czy istnieje sposób, aby to sprawne w python?Przetwarzanie dużych danych w pythonie

+2

Prawdopodobny duplikat: http://stackoverflow.com/questions/519633/lazy-method-for-- -fig-plik-w-python –

+3

Sprawdź pandy i pytables/hdf lub hadoop streaming z pythona. Jeśli używasz systemu Linux, możesz użyć dumbo, aby ułatwić interakcję z pytonem hadoop. Python ma silną i dynamiczną społeczność do analizy danych; trudno przeoczyć w wyszukiwarce Google. – agconti

+0

Nie dup, ale także pokrewne: [Python file iterator nad plikiem binarnym z nowszym idiomem] (http://stackoverflow.com/questions/4566498/python-file-iterator-over-a-binary-file-with- newer-idiom/4566523 # 4566523). – abarnert

Odpowiedz

19

Klucz ogólny polega na iteracyjnym przetwarzaniu pliku.

Jeśli masz do czynienia tylko z plikiem tekstowym, jest to banalne: for line in f: czyta się tylko w jednym wierszu na raz. (Właściwie to buforuje, ale bufory są na tyle małe, że nie musisz się o to martwić.)

Jeśli masz do czynienia z jakimś innym określonym typem pliku, takim jak numpy plik binarny, plik CSV , dokument XML itp., są na ogół podobne rozwiązania specjalne, ale nikt nie może ich opisać, chyba że powiesz nam, jakie masz dane.

Ale co, jeśli masz ogólny plik binarny?


Najpierw metoda read pobiera opcjonalne bajty maksymalne do odczytu. Tak więc, zamiast tego:

data = f.read() 
process(data) 

Można to zrobić:

while True: 
    data = f.read(8192) 
    if not data: 
     break 
    process(data) 

Możesz zamiast napisać funkcję tak:

def chunks(f): 
    while True: 
     data = f.read(8192) 
     if not data: 
      break 
     yield data 

Następnie można po prostu zrób to:

for chunk in chunks(f): 
    process(chunk) 

Można również zrobić to z dwóch argumentów iter, ale wiele osób uważa, że ​​nieco niejasne:

for chunk in iter(partial(f.read, 8192), b''): 
    process(chunk) 

Tak czy inaczej, ta opcja ma zastosowanie do wszystkich innych wariantów poniżej (z wyjątkiem pojedynczy mmap , która jest na tyle banalna, że ​​nie ma sensu).


Nie ma nic magicznego w tym numerze 8192. Zazwyczaj potrzebujesz mocy 2, a najlepiej wielokrotności rozmiaru strony systemu. poza tym, twoja wydajność nie zmieni się tak bardzo, niezależnie od tego, czy używasz 4KB, czy 4MB - a jeśli tak, to będziesz musiał przetestować to, co działa najlepiej w twoim przypadku użycia.


W każdym razie, zakłada się, że można przetworzyć każdy z 8K naraz bez zachowania jakiegokolwiek kontekstu. Jeśli np. Wysyłasz dane do progresywnego dekodera lub pliku nagłówkowego lub czegoś podobnego, jest to idealne.

Ale jeśli chcesz przetworzyć jeden "kawałek" na raz, twoje kawałki mogą kończyć się na granicy 8K. Jak sobie z tym radzisz?

To zależy od tego, jak fragmenty są rozdzielone w pliku, ale podstawowy pomysł jest dość prosty. Na przykład, powiedzmy, że używasz bajtów NUL jako separatora (mało prawdopodobne, ale łatwe do pokazania jako przykład zabawki).

data = b'' 
while True: 
    buf = f.read(8192) 
    if not buf: 
     process(data) 
     break 
    data += buf 
    chunks = data.split(b'\0') 
    for chunk in chunks[:-1]: 
     process(chunk) 
    data = chunks[-1] 

Ten rodzaj kodu jest bardzo powszechne w sieci (bo socketsnie tylko „przeczytać”, więc zawsze trzeba czytać w buforze i klocek do wiadomości), więc może znaleźć przydatne przykłady w kodzie sieciowym, który używa protokołu podobnego do twojego formatu pliku.


Alternatywnie można użyć mmap.

Jeśli rozmiar pamięci wirtualnej jest większy niż plik, to jest trywialne:

with mmap.mmap(f.fileno(), access=mmap.ACCESS_READ) as m: 
    process(m) 

Teraz m działa jak gigantyczny bytes obiektu, tak jak gdybyś zwanych read() aby przeczytać całość do pamięci - ale system operacyjny automatycznie zapisze i wyłączy bity w razie potrzeby.


Jeśli próbujesz odczytać plik zbyt duży, aby zmieścić się w wirtualnym wielkości pamięci (np plik 4GB z 32-bitowym Pythonie lub plik 20EB z 64-bitowym Pythona, który jest tylko może się wydarzyć w 2013 roku, jeśli czytasz rzadki lub wirtualny plik, na przykład plik VM dla innego procesu na Linuksie), musisz zaimplementować windowing-mmap w pliku na raz. Na przykład:

windowsize = 8*1024*1024 
size = os.fstat(f.fileno()).st_size 
for start in range(0, size, window size): 
    with mmap.mmap(f.fileno(), access=mmap.ACCESS_READ, 
        length=windowsize, offset=start) as m: 
     process(m) 

oczywiście okien mapowania ma taki sam problem jak czytanie fragmentów jeśli trzeba oddzielić rzeczy i można go rozwiązać w ten sam sposób.

Jednak jako optymalizacja, zamiast buforować, można po prostu przesunąć okno do strony zawierającej koniec ostatniej pełnej wiadomości, zamiast 8 MB za jednym razem, a następnie można uniknąć kopiowania. Jest to nieco bardziej skomplikowane, więc jeśli chcesz to zrobić, poszukaj czegoś takiego jak "przesuwne okno mmap" i napisz nowe pytanie, jeśli utkniesz.

+1

Pochwalam Cię za tak dobrze przemyślaną odpowiedź na tak szerokie pytanie. Poważnie, +1. – 2rs2ts

+0

Dzięki! W moim przypadku chciałbym, aby porcja była wielkości RAM ze względów wydajnościowych. Czy możesz to zrobić bez prób i błędów? – marshall

+0

@marshall: Naprawdę nie chcesz, aby był on wielkości (fizycznej) pamięci RAM, ponieważ część tej pamięci RAM jest potrzebna dla reszty przestrzeni interpretera, jądra, innych procesów, pamięci podręcznej dysku itp. kiedy zdobędziesz wystarczająco dużą porcję, nie uzyskasz znacznie więcej korzyści; jeśli twój kod jest tak blisko, że jest w pełni potokowany z DMA dysków, jak to możliwe, większe odczyty nie pomogą. Możesz (i powinieneś) przetestować go samemu, ale zazwyczaj ten słodki punkt będzie znajdować się pomiędzy 4KB a 8MB, a nie w pobliżu granicy fizycznej pamięci. – abarnert

Powiązane problemy