Ho fatto qualche ricerca su esattamente perché questo accade.
Nota: ho eseguito i miei test con Python 3.5. Python 2 ha un diverso sistema I/O con la stessa stranezza per un motivo simile, ma era più facile da capire con il nuovo sistema IO in Python 3.
Come risulta, questo è dovuto a Python's BufferedReader, non qualsiasi cosa sulle chiamate di sistema effettive.
Si può provare questo codice:
fp = open('/dev/urandom', 'rb')
fp = fp.detach()
ans = fp.read(65600)
fp.close()
Se si tenta di strace questo codice, troverete:
read(3, "]\"\34\277V\21\223$l\361\234\16:\306V\323\266M\215\331\3bdU\265C\213\227\225pWV"..., 65600) = 65600
nostro oggetto file originale era un BufferedReader:
>>> open("/dev/urandom", "rb")
<_io.BufferedReader name='/dev/urandom'>
Se chiamiamo detach()
su questo, poi buttiamo via la parte BufferedReader e prendiamo solo la Fi leIO, che è ciò che parla al kernel. A questo livello, leggerà tutto in una volta.
Quindi il comportamento che stiamo cercando è in BufferedReader. Possiamo cercare in Modules/_io/bufferedio.c
nel sorgente Python, in particolare nella funzione _io__Buffered_read_impl
. Nel nostro caso, dove il file non è stato ancora letto fino a questo momento, inviamo a _bufferedreader_read_generic
.
Ora, questo è dove la peculiarità vediamo proviene da:
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);
sostanza, questo leggerà altrettanti "blocchi" completa possibile direttamente nel buffer di uscita. La dimensione del blocco si basa sul parametro passato al costruttore BufferedReader
, che ha un valore predefinito selezionato da alcuni parametri:
* 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.
Quindi questo codice leggerà il più possibile senza bisogno di iniziare a riempire il buffer. Questo sarà 65536 byte in questo caso, perché è il più grande multiplo di 4096 byte inferiore o uguale a 65600.In questo modo, può leggere i dati direttamente nell'output ed evitare di riempire e svuotare il proprio buffer, che sarebbe più lento.
Una volta terminato, potrebbe esserci ancora un po 'di più da leggere. Nel nostro caso, 65600 - 65536 == 64
, quindi ha bisogno di leggere almeno 64 byte in più. Ma ancora legge 4096! Cosa dà? Bene, la chiave qui è che il punto di un BufferedReader è di minimizzare il numero di letture del kernel che dobbiamo effettivamente fare, poiché ogni lettura ha un sovraccarico significativo di per sé. Quindi legge semplicemente un altro blocco per riempire il suo buffer (quindi 4096 byte) e ti dà i primi 64 di questi.
Speriamo che abbia senso spiegare perché succede così.
A dimostrazione, potremmo provare questo programma:
import _io
fp = _io.BufferedReader(_io.FileIO("/dev/urandom", "rb"), 30000)
ans = fp.read(65600)
fp.close()
Con questo, strace ci dice:
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
Certo, basta, questo segue lo stesso schema: il maggior numero di blocchi possibile, e poi un altro.
dd
, in una ricerca di alta efficienza di copia di molti e molti dati, proveresti a leggere una quantità molto più grande in una volta, motivo per cui utilizza solo una lettura. Provalo con un set di dati più ampio e sospetto che potresti trovare più chiamate da leggere.
TL; DR: il BufferedReader legge quanti più blocchi completi possibile (64 * 4096) e quindi un blocco aggiuntivo di 4096 per riempire il buffer.
EDIT:
Il modo più semplice per modificare la dimensione del buffer, come @fcatho indicate, è quello di modificare l'argomento buffering
su open
:
open(name[, mode[, buffering]])
(...)
L'argomento facoltativo di buffering specifica la dimensione del buffer desiderata del file: 0 significa unbuffered, 1 significa bufferizzato dalla linea, qualsiasi altro valore positivo significa utilizzare un buffer di (circa) quella dimensione (i n byte). Un buffering negativo significa utilizzare l'impostazione predefinita di sistema, che di solito è la linea bufferizzata per i dispositivi tty e completamente bufferizzata per altri file. Se omesso, viene utilizzato il valore predefinito di sistema.
Questo funziona sia su Python 2 e Python 3.
Basta controllare le fonti !? –
@UlrichEckhardt, ci sono abbastanza sistemi in gioco qui che non tutti saprebbero da dove iniziare. –