Wenn ich Bytes aus einer Datei in ein Byte [] lese, sehe ich, dass die Performance von FileInputStream schlechter ist, wenn das Array etwa 1 MB im Vergleich zu 128 KB groß ist. An den 2 von mir getesteten Arbeitsplätzen ist es mit 128 KB fast doppelt so schnell. Warum ist das so?
%Vor%Ausgaben
%Vor%Beachten Sie, dass dies eine Neudefinition einer bereits geposteten Frage ist. Der andere war mit nicht verwandten Diskussionen belastet. Ich werde den anderen zum Löschen markieren.
Bearbeiten: Duplizieren, wirklich? Ich könnte sicher einen besseren Code machen, um meinen Punkt zu beweisen, aber das beantwortet meine Frage nicht
Edit2: Ich habe den Test mit jedem Puffer zwischen 5 KB und 1000 KB am
durchgeführt
Win7 / JRE 1.8.0_25 und die schlechte Leistung beginnt bei precis 508 KB und alle nachfolgenden. Sorry für die schlechten Diagramm Legionen, x ist Puffergröße, y ist Millisekunden
TL; DR Der Leistungsabfall wird durch die Speicherzuweisung und nicht durch Probleme beim Lesen von Dateien verursacht.
Ein typisches Benchmarking-Problem: Sie benchmarken eine Sache, messen aber tatsächlich eine andere.
Zuerst, als ich den Beispielcode mit RandomAccessFile
, FileChannel
und ByteBuffer.allocateDirect
umschrieb, ist der Schwellenwert verschwunden. Die Dateileseleistung wurde für 128 KB und 1 MB Puffer ungefähr gleich.
Im Gegensatz zum direkten ByteBuffer kann der I / O FileInputStream.read
keine Daten direkt in das Java-Byte-Array laden. Es muss zuerst Daten in einen nativen Puffer laden und dann mit JNI SetByteArrayRegion
function in Java kopieren.
Also müssen wir uns die native Implementierung von FileInputStream.read
ansehen. Es kommt auf das folgende Stück Code in io_util.c :
Hier BUF_SIZE == 8192. Wenn der Puffer größer als dieser reservierte Stapelbereich ist, wird ein temporärer Puffer von malloc
zugewiesen. Unter Windows malloc
wird normalerweise über HeapAlloc implementiert > WINAPI-Anruf.
Als Nächstes habe ich die Leistung von HeapAlloc
+ HeapFree
alleine ohne Datei-I / O gemessen. Die Ergebnisse waren interessant:
Wie Sie sehen können, ändert sich die Leistung der Speicherzuweisung des Betriebssystems drastisch bei 1 MB Grenze. Dies kann durch verschiedene Zuweisungsalgorithmen für kleine Brocken und für große Brocken erklärt werden.
AKTUALISIEREN
Die Dokumentation für HeapCreate bestätigt das Idee über eine bestimmte Zuordnungsstrategie für Blöcke größer als 1 MB (siehe dwMaximumSize Beschreibung).
Außerdem ist der größte Speicherblock, der vom Heapspeicher zugewiesen werden kann, etwas weniger als 512 KB für einen 32-Bit-Prozess und etwas weniger als 1,024 KB für einen 64-Bit-Prozess.
...
Anforderungen zum Zuweisen von Speicherblöcken, die größer als der Grenzwert für einen Heap mit fester Größe sind, schlagen nicht automatisch fehl; Stattdessen ruft das System die VirtualAlloc-Funktion auf, um den Speicher zu erhalten, der für große Blöcke benötigt wird.
Die optimale Puffergröße hängt von der Dateisystemblockgröße, der CPU-Cache-Größe und der Cache-Latenz ab. Die meisten os'es verwenden Blockgröße 4096 oder 8192, daher wird empfohlen, Puffer mit dieser Größe oder Multiplizität dieses Wertes zu verwenden.
Ich habe den Test neu geschrieben, um verschiedene Puffergrößen zu testen.
Hier ist der neue Code:
%Vor%Und hier sind die Ergebnisse ...
%Vor%Es sieht so aus, als ob bei einem Puffer von ungefähr 1021kB eine Art Schwellenwert erreicht wird. Wenn ich tiefer in das sehe, sehe ich ...
%Vor% Es sieht also so aus, als ob ein gewisser Verdoppelungseffekt auftritt, wenn dieser Schwellenwert erreicht wird. Meine anfänglichen Gedanken sind, dass die readToArray while-Schleife die doppelte Anzahl von Malen wiederholte, als der Schwellenwert erreicht wurde, aber das ist nicht der Fall, die while-Schleife durchläuft nur eine Iteration, egal ob 300ms oder 600ms laufen. Schauen wir uns also die tatsächliche io_utils.c
an, die die Daten für einige Hinweise tatsächlich von der Festplatte liest.
Zu beachten ist, dass BUF_SIZE auf 8192 eingestellt ist. Der Doubling-Effekt passiert weit darüber hinaus. Der nächste Schuldige wäre also die Methode IO_Read
.
Also gehen wir zur handleRead-Methode.
%Vor%Diese Methode übergibt die Anfrage an eine Methode namens ReadFile.
%Vor%Und hier läuft die Spur kalt ... vorerst. Wenn ich den Code für ReadFile finde, werde ich einen Blick darauf werfen und zurück posten.
Dies kann wegen CPU-Cache sein,
cpu hat einen eigenen Cache-Speicher und es gibt eine feste Größe, für die Sie Ihre CPU-Cache-Größe überprüfen können, indem Sie diesen Befehl auf cmd ausführen
wmic CPU erhält L2CacheSize
Angenommen, Sie haben 256k als CPU-Cache-Größe, Was passiert also? Wenn Sie 256k Chunks oder kleinere lesen, ist der Inhalt, der in den Puffer geschrieben wurde, immer noch im CPU-Cache, wenn der Lesezugriff darauf erfolgt. Wenn Sie Klumpen mehr von 256k dann der letzten 256k haben, die gelesen wurden, ist in der CPU-Cache, so dass, wenn die Lese von Anfang an beginnt der Inhalt muss aus dem Hauptspeicher abgerufen werden.
Tags und Links java performance io fileinputstream