2015-10-02 10 views
5

testowałem to:Dlaczego Python dzieli funkcję odczytu na wiele systemów?

strace python -c "fp = open('/dev/urandom', 'rb'); ans = fp.read(65600); fp.close()" 

z następującymi wyjściu częściowej:

read(3, "\211^\250\202P\32\344\262\373\332\241y\226\340\16\16!<\354\250\221\261\331\242\304\375\24\36\253!\345\311"..., 65536) = 65536 
read(3, "\7\220-\344\365\245\240\346\241>Z\330\266^Gy\320\275\231\30^\266\364\253\256\263\214\310\345\217\221\300"..., 4096) = 4096 

Istnieją dwa zaproszenia do odczytu syscall z różnej liczby żądanych bajtów.

Kiedy powtórzyć tę samą komendę przy użyciu dd,

dd if=/dev/urandom bs=65600 count=1 of=/dev/null 

tylko jeden odczyt syscall jest uruchamiany za pomocą dokładnej liczby bajtów wymaganych.

read(0, "P.i\246!\356o\10A\307\376\2332\365=\262r`\273\"\370\4\n!\364J\316Q1\346\26\317"..., 65600) = 65600 

Mam googleed to bez żadnego możliwego wyjaśnienia. Czy jest to związane z rozmiarem strony czy jakimkolwiek zarządzaniem pamięcią w Pythonie?

Dlaczego tak się dzieje?

+0

Sprawdź tylko źródła !? –

+1

@UlrichEckhardt, w grze jest wystarczająco dużo systemów, aby nie wszyscy wiedzieli, od czego zacząć. –

Odpowiedz

9

Zrobiłem kilka badań na temat tego, dlaczego tak się dzieje.

Uwaga: Wykonałem testy w Pythonie 3.5. Python 2 ma inny system I/O z tym samym dziwactwem z podobnego powodu, ale było to łatwiejsze do zrozumienia dzięki nowemu systemowi IO w Pythonie 3.

Jak się okazuje, jest to spowodowane buforowaniem Pythona, a nie cokolwiek na temat rzeczywistych wywołań systemowych.

Można spróbować tego kodu:

fp = open('/dev/urandom', 'rb') 
fp = fp.detach() 
ans = fp.read(65600) 
fp.close() 

Jeśli spróbujesz strace ten kod znajdziesz:

read(3, "]\"\34\277V\21\223$l\361\234\16:\306V\323\266M\215\331\3bdU\265C\213\227\225pWV"..., 65600) = 65600 

Nasz obiekt oryginalny plik był BufferedReader:

>>> open("/dev/urandom", "rb") 
<_io.BufferedReader name='/dev/urandom'> 

Jeśli zadzwonimy pod numer detach(), wyrzucimy część BufferedReader i otrzymamy Fi leIO, czyli to, co mówi do jądra. Na tej warstwie przeczyta wszystko na raz.

Tak więc zachowanie, którego szukamy, znajduje się w BufferedReader. Możemy szukać w Modules/_io/bufferedio.c w źródle Pythona, w szczególności funkcji _io__Buffered_read_impl. W naszym przypadku, w którym plik nie został jeszcze przeczytany do tego momentu, wysyłamy do _bufferedreader_read_generic.

Teraz to gdzie dziwactwo widzimy pochodzi z:

while (remaining > 0) { 
    /* We want to read a whole block at the end into buffer. 
     If we had readv() we could do this in one pass. */ 
    Py_ssize_t r = MINUS_LAST_BLOCK(self, remaining); 
    if (r == 0) 
     break; 
    r = _bufferedreader_raw_read(self, out + written, r); 

zasadniczo będzie to czytać jak najwięcej pełnych „bloki”, jak to możliwe bezpośrednio do bufora wyjściowego. Rozmiar bloku oparty jest na parametr przekazany do konstruktora BufferedReader, która ma domyślnie wybranego przez kilka parametrów:

 * Binary files are buffered in fixed-size chunks; the size of the buffer 
     is chosen using a heuristic trying to determine the underlying device's 
     "block size" and falling back on `io.DEFAULT_BUFFER_SIZE`. 
     On many systems, the buffer will typically be 4096 or 8192 bytes long. 

Więc ten kod będzie czytać jak najwięcej bez konieczności rozpocząć wypełnianie swój bufor. W tym przypadku będzie to 65536 bajtów, ponieważ jest to największa wielokrotność 4096 bajtów mniejsza lub równa 65600.Dzięki temu może odczytać dane bezpośrednio na wyjściu i uniknąć napełniania i opróżniania własnego bufora, który byłby wolniejszy.

Po wykonaniu tej czynności może być nieco więcej do przeczytania. W naszym przypadku, 65600 - 65536 == 64, więc musi przeczytać co najmniej 64 więcej bajtów. Ale mimo to czyta 4096! Co daje? Otóż ​​kluczem jest to, że celem BufferedReader jest zminimalizowanie liczby odczytów jądra, które faktycznie musimy wykonać, ponieważ każdy odczyt ma znaczny narzut w samym sobie. Więc po prostu czyta kolejny blok, aby wypełnić swój bufor (czyli 4096 bajtów) i daje ci pierwszych 64 z nich.

Mam nadzieję, że ma to sens, jeśli chodzi o wyjaśnianie, dlaczego tak się dzieje.

W demonstracji, możemy spróbować tego programu:

import _io 
fp = _io.BufferedReader(_io.FileIO("/dev/urandom", "rb"), 30000) 
ans = fp.read(65600) 
fp.close() 

z tym, strace mówi nam:

read(3, "\357\202{u'\364\6R\fr\20\f~\254\372\3705\2\332JF\n\210\341\2s\365]\270\r\306B"..., 60000) = 60000 
read(3, "\266_ \323\346\302}\32\334Yl\ry\215\326\222\363O\303\367\353\340\303\234\0\370Y_\3232\21\36"..., 30000) = 30000 

Rzeczywiście, to według takiego samego schematu: wiele bloków, jak to możliwe, a następnie jeszcze jeden.

dd, w dążeniu do wysokiej wydajności kopiowania partii i dużej ilości danych, spróbuje odczytywać znacznie większą ilość naraz, dlatego używa tylko jednego odczytu. Wypróbuj go z większym zestawem danych i podejrzewam, że możesz znaleźć wiele połączeń do przeczytania.

TL; DR: BufferedReader odczytuje jak najwięcej pełnych bloków (64 * 4096), a następnie jeden dodatkowy blok 4096, aby wypełnić swój bufor.

EDIT:

Prosty sposób, aby zmienić rozmiar bufora, jak @fcatho wskazał, jest zmiana buffering argumentu na open:

open(name[, mode[, buffering]]) 

(...)

Opcjonalny argument buforowania określa żądany rozmiar bufora pliku: 0 oznacza niebuforowany, 1 oznacza buforowany wiersz, dowolna inna wartość dodatnia oznacza użycie bufora (w przybliżeniu) tego rozmiaru (i n bajtów). Ujemne buforowanie oznacza użycie domyślnego systemu, który jest zwykle buforowany liniowo dla urządzeń tty i w pełni buforowany dla innych plików. Jeśli zostanie pominięty, zostanie użyte domyślne ustawienie systemowe.

Działa to zarówno na Python 2 i Python 3.

+1

Niezłe wyjaśnienie! Jednak znalazłem pokrewną dokumentację na temat tego zachowania w dokumentach Pythona https://docs.python.org/2/library/functions.html#open Istnieje trzeci parametr w funkcji otwartej, który obsługuje buforowanie. – fcatho

+0

@fcatho Dobry połów. Zapomniałem o tym. Dodałem odwołanie do dokumentów. –

Powiązane problemy