Java FileOutputStream konsekutiver Abschluss dauert sehr lange

9

Ich stehe vor einer etwas seltsamen Situation.

Ich kopiere von FileInputStream nach FileOutputStream eine Datei, die ungefähr 500 MB groß ist. Es geht ziemlich gut (dauert ca. 500ms). Wenn ich diesen FileOutputStream die FIRST Zeit schließe, dauert es ungefähr 1ms.

Aber hier kommt der Haken, wenn ich das nochmal durchführe, dauert jede aufeinanderfolgende Schließung etwa 1500-2000ms! Die Dauer wird auf 1 ms zurückgesetzt, wenn ich diese Datei lösche.

Gibt es einige wesentliche java.io Kenntnisse, die ich vermisse?

Es scheint mit OS verwandt zu sein. Ich laufe auf ArchLinux (der gleiche Code, der unter Windows 7 ausgeführt wird, hat alle Zeiten unter 20ms). Beachten Sie, dass es keine Rolle spielt, ob es in OpenJDK oder Oracle JDK ausgeführt wird. Festplatte ist ein Solid-State-Laufwerk mit ext4-Dateisystem.

Hier ist mein Testcode:

%Vor%

Die Ausgabe ist dann:

%Vor%     
d1x 14.08.2014, 12:59
quelle

3 Antworten

0

Beachten Sie, dass diese Frage gestellt wurde, weil ich neugierig war, warum das passiert, es war nicht die Messung des Kopierdurchsatzes.

Zusammenfassend:

Wie EJP bemerkt wurde, ist die ganze Sache nicht mit Java verbunden . Das Ergebnis ist das gleiche, wenn mehrere aufeinanderfolgende cp Befehle im Bash-Skript ausgeführt werden.

Die beste Antwort, warum dies passiert, ist Stephen - fsync zwischen Kopieraufrufen entfernt das Problem (aber fsync selbst dauert ~ 2.5s).

Der beste Weg, dies zu lösen, ist, es als Files.copy(I, o, REPLACE_EXISTING) (wie in Joop ) zu tun ) = & gt; Zuerst prüfen, ob die Zieldatei existiert und wenn ja, löschen Sie sie (statt "überschreiben"). Dann können Sie schnell schreiben und schließen.

    
d1x 15.08.2014, 08:38
quelle
2

@Duncan hat die folgende Erklärung vorgeschlagen:

  

Der erste Aufruf von close () kehrt schnell zurück, aber das Betriebssystem spült immer noch Daten auf die Festplatte. Die nachfolgenden Aufrufe von close () können erst abgeschlossen werden, wenn das vorherige Löschen abgeschlossen ist.

Ich denke, das ist nah an der Marke, aber nicht genau richtig.

Ich denke, was hier eigentlich passiert ist, dass die erste Kopie den Pufferspeicher-Cache des Betriebssystems mit einer großen Anzahl von schmutzigen Seiten füllt. Der interne Daemon, der die schmutzigen Seiten auf Disks spült, fängt an, an ihnen zu arbeiten, läuft aber immer noch, wenn Sie die zweite Kopie starten.

Wenn Sie die zweite Kopie machen, versucht das Betriebssystem Puffer-Cache-Seiten zum Lesen und Schreiben zu erfassen. Aber da der Puffer-Cache voll von schmutzigen Seiten ist, werden die Lese- und Schreib-Aufrufe wiederholt blockiert, was darauf wartet, dass freie Seiten verfügbar werden. Bevor jedoch eine fehlerhafte Seite wiederverwendet werden kann, müssen die Daten auf der Seite auf die Disc geschrieben werden. Das Nettoergebnis ist, dass die Kopie auf die effektive Datenschreibrate verlangsamt.

Eine 30-Sekunden-Pause reicht möglicherweise nicht aus, um die schmutzigen Seiten auf die Disc zu laden.

Eine Sache, die Sie versuchen könnten, ist ein fsync(fd) oder fdatasync(fd) zwischen den Kopien zu machen. In Java können Sie dazu FileDescriptor.sync() aufrufen.

Jetzt kann ich nicht sagen, ob dies den Gesamtdurchsatz der Kopie verbessern wird, aber ich würde erwarten, dass eine sync -Operation besser (nur) eine Datei schreibt als der Seitenräumungsalgorithmus es.

    
Stephen C 14.08.2014 13:33
quelle
1

Sie scheinen etwas interessantes zu sehen. Unter Linux darf jemand ein Datei-Handle auf der Originaldatei halten, wenn man es öffnet, den Verzeichniseintrag tatsächlich löscht und neu startet. Dies stört die ursprüngliche Datei (Handle) nicht. Beim Schließen als, möglicherweise einige Plattenverzeichnis Arbeit.

Testen Sie es mit IOUtils.copyLarge und Files.copy:

%Vor%

(Ich habe einmal eine IOUtils.copy gesehen, die nur copyLarge aufgerufen hat, aber Files.copy sollte gut funktionieren.)

    
Joop Eggen 14.08.2014 13:44
quelle

Tags und Links