it-swarm.com.de

Wie bestimmen Sie die ideale Puffergröße bei Verwendung von FileInputStream?

Ich habe eine Methode, die ein MessageDigest (einen Hash) aus einer Datei erstellt, und ich muss dazu eine Menge Dateien erstellen (> = 100.000). Wie groß sollte ich den Puffer verwenden, um aus den Dateien zu lesen, um die Leistung zu maximieren?

Jeder kennt den grundlegenden Code (den ich hier nur wiederholen werde):

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();

Was ist die ideale Größe des Puffers, um den Durchsatz zu maximieren? Ich weiß, dass dies systemabhängig ist, und ich bin mir ziemlich sicher, dass das Betriebssystem, das Dateisystem, und von der Festplatte abhängig sind, und möglicherweise andere Hardware/Software im Mix. 

(Ich sollte darauf hinweisen, dass ich etwas neu in Java bin, daher kann dies nur ein Java-API-Aufruf sein, von dem ich nichts weiß.)

Edit: Ich weiß nicht im Voraus, auf welche Art von Systemen dies angewendet wird, daher kann ich nicht viel davon ausgehen. (Ich verwende Java aus diesem Grund.)

Edit: Im obigen Code fehlen Dinge wie try..catch, um den Beitrag kleiner zu machen

132
ARKBAN

Die optimale Puffergröße hängt mit einer Reihe von Faktoren zusammen: Blockgröße des Dateisystems, CPU-Cache-Größe und Cache-Latenz.

Die meisten Dateisysteme sind so konfiguriert, dass sie Blockgrößen von 4096 oder 8192 verwenden. Wenn Sie die Puffergröße so konfigurieren, dass Sie einige Bytes mehr als den Plattenblock lesen, können die Vorgänge mit dem Dateisystem äußerst ineffizient sein (z. B. wenn Sie Sie haben Ihren Puffer so konfiguriert, dass 4100 Bytes gleichzeitig gelesen werden. Jeder Lesevorgang würde 2 Blocklesevorgänge durch das Dateisystem erfordern. Wenn sich die Blöcke bereits im Cache befinden, zahlen Sie den Preis für die RAM -> L3/L2-Cache-Latenz. Wenn Sie Pech haben und die Blöcke noch nicht im Cache sind, zahlen Sie auch den Preis für die Disk-> RAM-Latenz.

Aus diesem Grund sehen Sie die meisten Puffer in der Größe einer Potenz von 2 und im Allgemeinen größer als (oder gleich) der Festplattenblockgröße. Dies bedeutet, dass einer Ihrer Stream-Lesevorgänge mehrere Festplattenblock-Lesevorgänge zur Folge haben kann. Bei diesen Lesevorgängen wird jedoch immer ein vollständiger Block verwendet - keine verschwendeten Lesevorgänge.

In einem typischen Streaming-Szenario ist dies ein gutes Stück verschoben, da der Block, der von der Festplatte gelesen wird, sich beim nächsten Lesevorgang noch im Speicher befindet (wir führen hier schließlich sequentielle Lesevorgänge durch) Bezahlen des Preises für die RAM -> L3/L2-Cache-Latenz beim nächsten Lesevorgang, jedoch nicht für die Latenz zwischen Festplatte und RAM. In Bezug auf die Größenordnung ist die Latenz von Festplatte zu RAM so langsam, dass sie jede andere Latenz überfordert, mit der Sie möglicherweise zu tun haben.

Wenn Sie also einen Test mit unterschiedlichen Cachegrößen durchgeführt haben (dies selbst nicht getan haben), werden Sie wahrscheinlich einen großen Einfluss der Cachegröße bis zur Größe des Dateisystemblocks feststellen. Darüber hinaus vermute ich, dass sich die Dinge ziemlich schnell beruhigen würden.

Es gibt eine Tonne von Bedingungen und Ausnahmen hier - die Komplexität des Systems ist tatsächlich ziemlich schwankend (nur einen Griff auf L3 bekommen -> L2-Cache-Übertragungen ist verblüffend komplex, und es ändert sich mit jedem CPU-Typ).

Dies führt zur "realen" Antwort: Wenn Ihre App zu 99% verfügbar ist, setzen Sie die Cache-Größe auf 8192 und fahren Sie fort (noch besser, wählen Sie Kapselung über Leistung und verwenden Sie BufferedInputStream, um die Details auszublenden). Wenn Sie zu 1% von Apps gehören, die in hohem Maße vom Datendurchsatz abhängen, können Sie Ihre Implementierung so gestalten, dass Sie verschiedene Strategien für die Datenträgerinteraktion austauschen und die Knöpfe und Drehregler bereitstellen, mit denen Ihre Benutzer testen und optimieren können (oder einige entwickeln können) selbstoptimierendes System).

198
Kevin Day

Ja, es hängt wahrscheinlich von verschiedenen Dingen ab - aber ich bezweifle, dass es einen großen Unterschied machen wird. Ich neige dazu, 16K oder 32K zu wählen, da es eine gute Balance zwischen Speicherauslastung und Leistung darstellt.

Beachten Sie, dass Sie einen try/finally-Block im Code haben sollten, um sicherzustellen, dass der Stream auch dann geschlossen wird, wenn eine Ausnahme ausgelöst wird.

14
Jon Skeet

In den meisten Fällen spielt es eigentlich keine Rolle. Wählen Sie einfach eine gute Größe wie 4K oder 16K und bleiben Sie dabei. Wenn Sie positiv dass dies der Engpass in Ihrer Anwendung ist, sollten Sie mit der Profilerstellung beginnen, um die optimale Puffergröße zu finden. Wenn Sie eine zu kleine Größe auswählen, verschwenden Sie Zeit für zusätzliche E/A-Vorgänge und zusätzliche Funktionsaufrufe. Wenn Sie eine Größe wählen, die zu groß ist, werden Sie viele Cache-Fehler sehen, die Sie wirklich verlangsamen werden. Verwenden Sie keinen Puffer, der größer als der L2-Cache ist.

7
Adam Rosenfield

Im Idealfall sollten wir über genügend Speicher verfügen, um die Datei in einem einzigen Lesevorgang lesen zu können. Dies wäre die beste Leistung, da das System das Dateisystem, die Zuordnungseinheiten und die Festplatte nach Belieben verwalten kann Wenn Sie die Dateigrößen im Voraus kennen möchten, verwenden Sie einfach die durchschnittliche Dateigröße, die auf 4 KByte (Standardzuordnungseinheit für NTFS) aufgerundet wird. Das Beste: Erstellen Sie einen Benchmark, um mehrere Optionen zu testen. 

4
Ovidiu Pacurar

Sie können die BufferedStreams/-leser verwenden und dann deren Puffergröße verwenden.

Ich glaube, die BufferedXStreams verwenden 8192 als Puffergröße, aber wie Ovidiu sagte, sollten Sie wahrscheinlich einen ganzen Haufen Optionen testen. Es wird wirklich von dem Dateisystem und der Festplattenkonfiguration abhängen, welche die besten Größen sind.

4
John Gardner

Das Lesen von Dateien mit FileChannel und MappedByteBuffer von Java NIO führt höchstwahrscheinlich zu einer Lösung, die wesentlich schneller ist als jede Lösung, die FileInputStream beinhaltet. Grundsätzlich sollten Sie große Dateien im Speicher zuordnen und für kleine Puffer direkte Puffer verwenden.

4
Alexander

In der Quelle von BufferedInputStream finden Sie: private static int DEFAULT_BUFFER_SIZE = 8192;
Es ist also in Ordnung, diesen Standardwert zu verwenden.
Wenn Sie mehr Informationen herausfinden können, erhalten Sie wertvollere Antworten.
Zum Beispiel bevorzugt Ihr ADSL einen Puffer von 1454 Bytes, was auf die Nutzlast von TCP/IP zurückzuführen ist. Für Festplatten können Sie einen Wert verwenden, der der Blockgröße Ihrer Festplatte entspricht.

1
GoForce5500

Wie bereits in anderen Antworten erwähnt, verwenden Sie BufferedInputStreams.

Danach denke ich, dass die Puffergröße keine Rolle spielt. Entweder ist das Programm E/A-gebunden und die wachsende Puffergröße über dem BIS-Standard hat keinen großen Einfluss auf die Leistung.

Oder das Programm ist CPU-gebunden in MessageDigest.update (), und die meiste Zeit wird nicht im Anwendungscode verwendet.

(Hmm ... bei mehreren Kernen könnten Threads helfen.)

1
Maglob

1024 ist für eine Vielzahl von Situationen geeignet, obwohl Sie in der Praxis mit einer größeren oder kleineren Puffergröße eine bessere Leistung feststellen können. 

Dies hängt von einer Reihe von Faktoren ab, darunter der Größe des Dateisystemblocks Und der CPU-Hardware.

Es ist auch üblich, für die Puffergröße eine Potenz von 2 zu wählen, da die meiste zugrunde liegende Hardware aus fle-Block- und Cache-Größen besteht, die eine Potenz von 2 sind. Mit den Buffered-Klassen können Sie die Puffergröße angeben im Konstruktor. Wenn keine angegeben ist, verwenden sie Einen Standardwert, der in den meisten JVMs eine Potenz von 2 ist.

Unabhängig davon, für welche Puffergröße Sie sich entscheiden, wird die größte Leistungssteigerung, die Sie sehen werden, vom ungepufferten zum gepufferten Dateizugriff. Durch das Anpassen der Puffergröße kann die Leistung geringfügig verbessert werden. Wenn Sie jedoch einen extrem kleinen oder extrem großen Puffer verwenden, ist es unwahrscheinlich, dass dies eine signifikante Auswirkung hat.

0
Adrian Krebs