Come si determina la dimensione del buffer ideale quando si utilizza FileInputStream?


156

Ho un metodo che crea un MessageDigest (un hash) da un file e devo farlo su molti file (> = 100.000). Quanto dovrei fare il buffer utilizzato per leggere dai file per massimizzare le prestazioni?

Quasi tutti hanno familiarità con il codice di base (che ripeterò qui per ogni evenienza):

MessageDigest md = MessageDigest.getInstance( "SHA" );
FileInputStream ios = new FileInputStream( "myfile.bmp" );
byte[] buffer = new byte[4 * 1024]; // what should this value be?
int read = 0;
while( ( read = ios.read( buffer ) ) > 0 )
    md.update( buffer, 0, read );
ios.close();
md.digest();

Qual è la dimensione ideale del buffer per massimizzare la produttività? So che questo dipende dal sistema e sono abbastanza sicuro che dipenda dal suo sistema operativo, dal FileSystem e dall'HDD e forse ci sono altri hardware / software nel mix.

(Devo sottolineare che sono un po 'nuovo di Java, quindi potrebbe trattarsi solo di una chiamata API Java di cui non sono a conoscenza.)

Modifica: non conosco in anticipo i tipi di sistemi su cui verrà utilizzato, quindi non posso supporre molto. (Sto usando Java per questo motivo.)

Modifica: Nel codice sopra mancano cose come try..catch per rendere il post più piccolo

Risposte:


213

La dimensione ottimale del buffer dipende da una serie di fattori: dimensione del blocco del file system, dimensione della cache della CPU e latenza della cache.

La maggior parte dei file system è configurata per utilizzare blocchi di dimensioni 4096 o 8192. In teoria, se si configura la dimensione del buffer in modo da leggere qualche byte in più rispetto al blocco del disco, le operazioni con il file system possono essere estremamente inefficienti (ad esempio se si configurato il buffer per leggere 4100 byte alla volta, ogni lettura richiederebbe 2 letture di blocchi dal file system). Se i blocchi sono già nella cache, finisci per pagare il prezzo della RAM -> latenza cache L3 / L2. Se sei sfortunato e i blocchi non sono ancora nella cache, paghi anche il prezzo della latenza del disco-> RAM.

Questo è il motivo per cui vedi la maggior parte dei buffer dimensionati come una potenza di 2 e generalmente più grandi (o uguali) della dimensione del blocco del disco. Ciò significa che una delle tue letture dello stream potrebbe comportare letture multiple del blocco del disco - ma quelle letture utilizzeranno sempre un blocco completo - nessuna lettura sprecata.

Ora, questo è un po 'compensato in uno scenario di streaming tipico perché il blocco letto dal disco sarà ancora in memoria quando si preme la lettura successiva (stiamo facendo letture sequenziali qui, dopo tutto) - quindi si finisce pagando la RAM -> L3 / L2 prezzo di latenza della cache alla lettura successiva, ma non la latenza del disco-> RAM. In termini di ordine di grandezza, la latenza del disco-> RAM è così lenta che praticamente inonda qualsiasi altra latenza con cui potresti avere a che fare.

Quindi, sospetto che se hai eseguito un test con dimensioni della cache diverse (non l'ho fatto da solo), probabilmente troverai un grande impatto della dimensione della cache fino alla dimensione del blocco del file system. Inoltre, sospetto che le cose si livellerebbero abbastanza rapidamente.

Ci sono un sacco di condizioni ed eccezioni qui - le complessità del sistema sono in realtà piuttosto sconcertanti (solo ottenere un controllo su L3 -> I trasferimenti di cache L2 è incredibilmente complesso e cambia con ogni tipo di CPU).

Questo porta alla risposta del "mondo reale": se la tua app è simile al 99% là fuori, imposta la dimensione della cache su 8192 e vai avanti (ancora meglio, scegli l'incapsulamento rispetto alle prestazioni e usa BufferedInputStream per nascondere i dettagli). Se fai parte dell'1% delle app che dipendono fortemente dalla velocità effettiva del disco, crea la tua implementazione in modo da poter scambiare diverse strategie di interazione del disco e fornire le manopole e i quadranti per consentire agli utenti di testare e ottimizzare (o trovare alcuni sistema di ottimizzazione automatica).


3
Ho eseguito alcuni banchmarking su un telefono cellulare (Nexus 5X) per la mia app Android per entrambi: file piccoli (3,5 Mb) e file grandi (175 Mb). E ho scoperto che la dimensione aurea sarebbe byte [] di 524288 lunghezze. Bene, potresti vincere 10-20ms se passi dal piccolo buffer 4Kb al grande buffer 524Kb a seconda della dimensione del file ma non ne vale la pena. Quindi 524 Kb era l'opzione migliore nel mio caso.
Kirill Karmazin,

19

Sì, probabilmente dipende da varie cose, ma dubito che farà molta differenza. Tendo a optare per 16K o 32K come un buon equilibrio tra utilizzo della memoria e prestazioni.

Nota che dovresti avere un blocco try / finally nel codice per assicurarti che il flusso sia chiuso anche se viene generata un'eccezione.


Ho modificato il post sul try..catch. Nel mio vero codice ne ho uno, ma l'ho lasciato fuori per rendere il post più breve.
ARKBAN,

1
se vogliamo definire una dimensione fissa per essa, quale dimensione è migliore? 4k, 16k o 32k?
BattleTested

2
@MohammadrezaPanahi: Per favore, non usare commenti agli utenti di tasso. Hai aspettato meno di un'ora prima di un secondo commento. Ricorda che gli utenti possono essere facilmente addormentati o alle riunioni o sostanzialmente impegnati con altre cose e hanno l'obbligo zero di rispondere ai commenti. Ma per rispondere alla tua domanda: dipende interamente dal contesto. Se stai eseguendo un sistema molto limitato di memoria, probabilmente vuoi un piccolo buffer. Se si esegue su un sistema di grandi dimensioni, l'utilizzo di un buffer più grande ridurrà il numero di chiamate in lettura. La risposta di Kevin Day è molto buona.
Jon Skeet,

7

Nella maggior parte dei casi, non importa molto. Basta scegliere una buona dimensione come 4K o 16K e attenersi ad essa. Se sei sicuro che questo è il collo di bottiglia nella tua applicazione, allora dovresti iniziare a profilare per trovare la dimensione ottimale del buffer. Se scegli una dimensione troppo piccola, perderai tempo facendo operazioni di I / O extra e chiamate di funzioni extra. Se scegli una dimensione troppo grande, inizierai a vedere molti errori della cache che ti rallenteranno davvero. Non utilizzare un buffer più grande della dimensione della cache L2.


4

Nel caso ideale dovremmo avere memoria sufficiente per leggere il file in una sola operazione di lettura. Sarebbe la migliore performance perché permettiamo al sistema di gestire File System, unità di allocazione e HDD a piacimento. In pratica hai la fortuna di conoscere in anticipo le dimensioni del file, basta usare la dimensione media del file arrotondata per eccesso a 4K (unità di allocazione predefinita su NTFS). E soprattutto: creare un benchmark per testare più opzioni.


vuoi dire che la dimensione del buffer migliore per leggere e scrivere in un file è 4k?
BattleTested

4

È possibile utilizzare i flussi / lettori Buffered e quindi utilizzare le dimensioni del buffer.

Credo che i BufferedXStreams stiano usando 8192 come dimensione del buffer, ma come ha detto Ovidiu, probabilmente dovresti eseguire un test su un sacco di opzioni. Dipenderà davvero dal filesystem e dalle configurazioni del disco su quali siano le dimensioni migliori.


4

La lettura dei file utilizzando FileChannel e MappedByteBuffer di Java NIO porterà molto probabilmente a una soluzione che sarà molto più veloce di qualsiasi soluzione che coinvolga FileInputStream. Fondamentalmente, mappare in memoria file di grandi dimensioni e utilizzare buffer diretti per quelli piccoli.


4

Nella fonte BufferedInputStream troverai: int statico privato DEFAULT_BUFFER_SIZE = 8192;
Quindi è giusto usare quel valore predefinito.
Ma se riesci a capire qualche informazione in più otterrai risposte più preziose.
Ad esempio, il tuo adsl potrebbe preferire un buffer di 1454 byte, questo perché il payload del TCP / IP. Per i dischi, è possibile utilizzare un valore corrispondente alla dimensione del blocco del disco.


1

Come già accennato in altre risposte, utilizzare BufferedInputStreams.

Dopodiché, immagino che la dimensione del buffer non abbia davvero importanza. O il programma è associato all'I / O e l'aumento della dimensione del buffer rispetto all'impostazione predefinita della BRI non avrà alcun impatto significativo sulle prestazioni.

Oppure il programma è associato alla CPU all'interno di MessageDigest.update () e la maggior parte del tempo non viene impiegata nel codice dell'applicazione, pertanto l'ottimizzazione non aiuta.

(Hmm ... con più core, i thread potrebbero aiutare.)


0

1024 è appropriato per un'ampia varietà di circostanze, sebbene in pratica si possano vedere prestazioni migliori con una dimensione del buffer maggiore o minore.

Ciò dipende da una serie di fattori, tra cui la dimensione del blocco del file system e l'hardware della CPU.

È anche comune scegliere una potenza di 2 per la dimensione del buffer, poiché la maggior parte dell'hardware sottostante è strutturato con dimensioni di blocchi e cache fle che sono una potenza di 2. Le classi bufferizzate consentono di specificare la dimensione del buffer nel costruttore. Se non ne viene fornito nessuno, usano un valore predefinito, che è una potenza di 2 nella maggior parte delle JVM.

Indipendentemente dalla dimensione del buffer scelta, il più grande aumento delle prestazioni che vedrai si sta spostando dall'accesso ai file senza buffer a quello con buffer. La regolazione della dimensione del buffer può migliorare leggermente le prestazioni, ma se non si utilizza una dimensione del buffer estremamente piccola o estremamente grande, è improbabile che abbia un impatto significativo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.