Domanda

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 suo FileSystem, dal e HDD, e forse ci sono altri hardware / software nel mix.

(Devo sottolineare che sono un po 'nuovo su 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 verranno utilizzati, quindi non posso supporre molto. (Sto usando Java per questo motivo.)

Modifica: Nel codice sopra mancano elementi come try..catch per ridurre le dimensioni del post

È stato utile?

Soluzione

La dimensione ottimale del buffer dipende da una serie di aspetti: 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 ( cioè se hai configurato il tuo 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 e della RAM.

Questo è il motivo per cui vedi la maggior parte dei buffer dimensionati come 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 è leggermente 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, dopo tutto) - quindi finisci per pagare la RAM - > Prezzo di latenza della cache L3 / L2 alla lettura successiva, ma non la latenza della RAM del disco. In termini di ordine di grandezza, la latenza della RAM del disco e del disco è così lenta che praticamente inonda qualsiasi altra latenza che 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 una tonnellata 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 è del 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).

Altri suggerimenti

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 lo stream sia chiuso anche se viene generata un'eccezione.

Nella maggior parte dei casi, non importa molto. Basta scegliere una buona dimensione come 4K o 16K e attenersi ad essa. Se sei positivo 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.

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.

È possibile utilizzare BufferedStreams / lettori 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.

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, mappa la memoria di file di grandi dimensioni e usa buffer diretti per quelli piccoli.

Nella sorgente di BufferedInputStream troverai: statico privato int DEFAULT_BUFFER_SIZE = 8192;
Quindi è giusto che tu usi 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.

Come già accennato in altre risposte, utilizzare BufferedInputStreams.

Dopodiché, immagino che la dimensione del buffer non abbia 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.

O il programma è associato alla CPU all'interno di MessageDigest.update () e la maggior parte del tempo non viene impiegata nel codice dell'applicazione, pertanto la modifica non sarà utile.

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

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, incluso il blocco del file system dimensioni e hardware della CPU.

È anche comune scegliere una potenza di 2 per la dimensione del buffer, poiché la maggior parte sottostante l'hardware è strutturato con blocchi di fle e dimensioni della cache che sono una potenza di 2. Il buffer le classi consentono di specificare la dimensione del buffer nel costruttore. Se nessuno è fornito, loro usa 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 sarà see 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 a meno che non si stia utilizzando un dispositivo estremamente piccolo o estremamente buffer di grandi dimensioni, è improbabile che abbia un impatto significativo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top