2015-10-02 19 views
5

Ho testato questo:Perché Python divide la funzione di lettura in più syscalls?

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

Con il seguente output parziale:

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 

Esistono due inviti a lettura syscall con diverso numero di byte richiesti.

Quando ripeto lo stesso comando utilizzando dd,

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

solo una chiamata di sistema di lettura viene attivato utilizzando il numero esatto di byte richiesti.

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

Ho cercato su Google questo senza alcuna spiegazione possibile. Questo è legato alle dimensioni della pagina o a qualsiasi gestione della memoria Python?

Perché succede?

+0

Basta controllare le fonti !? –

+1

@UlrichEckhardt, ci sono abbastanza sistemi in gioco qui che non tutti saprebbero da dove iniziare. –

risposta

9

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.

+1

Bella spiegazione! Tuttavia, ho trovato alcuni documenti relativi a questo comportamento nei documenti Python https://docs.python.org/2/library/functions.html#open C'è un terzo parametro nella funzione aperta che gestisce il buffering. – fcatho

+0

@fcatho Buona cattura. Me ne sono dimenticato. Ho aggiunto un riferimento ai documenti. –

Problemi correlati