Java NIO FileChannel im Vergleich zur Leistung / Nützlichkeit von FileOutputstream

168

Ich versuche herauszufinden, ob es einen Unterschied in der Leistung (oder den Vorteilen) gibt, wenn wir nio im FileChannelVergleich zu normal verwenden FileInputStream/FileOuputStream, um Dateien in das Dateisystem zu lesen und zu schreiben. Ich habe festgestellt, dass beide auf meiner Maschine auf dem gleichen Niveau arbeiten, auch um ein Vielfaches FileChannellangsamer. Kann ich bitte weitere Einzelheiten zum Vergleich dieser beiden Methoden erfahren? Hier ist der Code, den ich verwendet habe. Die Datei, mit der ich teste, ist in der Nähe 350MB. Ist es eine gute Option, NIO-basierte Klassen für Datei-E / A zu verwenden, wenn ich keinen Direktzugriff oder andere solche erweiterten Funktionen betrachte?

package trialjavaprograms;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class JavaNIOTest {
    public static void main(String[] args) throws Exception {
        useNormalIO();
        useFileChannel();
    }

    private static void useNormalIO() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        InputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        byte[] buf = new byte[64 * 1024];
        int len = 0;
        while((len = is.read(buf)) != -1) {
            fos.write(buf, 0, len);
        }
        fos.flush();
        fos.close();
        is.close();
        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }

    private static void useFileChannel() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        FileInputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        FileChannel f = is.getChannel();
        FileChannel f2 = fos.getChannel();

        ByteBuffer buf = ByteBuffer.allocateDirect(64 * 1024);
        long len = 0;
        while((len = f.read(buf)) != -1) {
            buf.flip();
            f2.write(buf);
            buf.clear();
        }

        f2.close();
        f.close();

        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }
}
Keshav
quelle
5
transferToIch transferFromwäre konventioneller zum Kopieren von Dateien. Welche Technik auch immer Ihre Festplatte nicht schneller oder langsamer machen sollte, obwohl ich denke, dass es ein Problem geben könnte, wenn sie kleine Stücke gleichzeitig liest und den Kopf übermäßig viel Zeit damit verbringt, zu suchen.
Tom Hawtin - Tackline
1
(Sie erwähnen nicht, welches Betriebssystem Sie verwenden oder welchen JRE-Anbieter und welche Version.)
Tom Hawtin - Tackline
Ups sorry, ich verwende FC10 mit Sun JDK6.
Keshav

Antworten:

202

Meine Erfahrung mit größeren Dateien war java.nioschneller als java.io. Solide schneller. Wie im Bereich> 250%. Trotzdem eliminiere ich offensichtliche Engpässe, unter denen Ihr Mikro-Benchmark leiden könnte. Mögliche Untersuchungsgebiete:

Die Puffergröße. Der Algorithmus, den Sie im Grunde haben, ist

  • Kopie von der Festplatte in den Puffer
  • Kopieren vom Puffer auf die Festplatte

Meine eigene Erfahrung hat gezeigt, dass diese Puffergröße reif für die Optimierung ist. Ich habe mich für einen Teil meiner Anwendung auf 4 KB und für einen anderen auf 256 KB festgelegt. Ich vermute, Ihr Code leidet unter einem so großen Puffer. Führen Sie einige Benchmarks mit Puffern von 1 KB, 2 KB, 4 KB, 8 KB, 16 KB, 32 KB und 64 KB aus, um dies selbst zu beweisen.

Führen Sie keine Java-Benchmarks durch, die auf derselben Festplatte lesen und schreiben.

Wenn Sie dies tun, vergleichen Sie die Festplatte wirklich und nicht Java. Ich würde auch vorschlagen, dass Sie wahrscheinlich einen anderen Engpass haben, wenn Ihre CPU nicht ausgelastet ist.

Verwenden Sie keinen Puffer, wenn Sie nicht müssen.

Warum in den Speicher kopieren, wenn Ihr Ziel eine andere Festplatte oder eine Netzwerkkarte ist? Bei größeren Dateien ist die Latenz nicht trivial.

Wie andere gesagt haben, verwenden Sie FileChannel.transferTo()oder FileChannel.transferFrom(). Der Hauptvorteil hierbei ist, dass die JVM den Zugriff des Betriebssystems auf DMA ( Direct Memory Access ) verwendet, falls vorhanden. (Dies ist implementierungsabhängig, aber moderne Sun- und IBM-Versionen mit Allzweck-CPUs sind einsatzbereit.) Die Daten werden direkt zur / von der Disc, zum Bus und dann zum Ziel übertragen, wobei alle Schaltkreise umgangen werden RAM oder die CPU.

Die Web-App, an der ich Tag und Nacht gearbeitet habe, ist sehr schwer. Ich habe auch Mikro-Benchmarks und reale Benchmarks durchgeführt. Und die Ergebnisse sind auf meinem Blog zu sehen.

Verwenden Sie Produktionsdaten und -umgebungen

Mikro-Benchmarks neigen zu Verzerrungen. Wenn Sie können, bemühen Sie sich, Daten von genau dem zu sammeln, was Sie planen, mit der erwarteten Belastung der erwarteten Hardware.

Meine Benchmarks sind solide und zuverlässig, da sie auf einem Produktionssystem, einem bulligen System, einem System unter Last, das in Protokollen gesammelt wurde, durchgeführt wurden. Nicht das 2,5-Zoll-SATA-Laufwerk mit 7200 U / min meines Notebooks, während ich intensiv beobachtete, wie die JVM meine Festplatte bearbeitete.

Worauf rennst du? Es ist wichtig.

Stu Thompson
quelle
@ Stu Thompson - Danke für deinen Beitrag. Ich bin auf Ihre Antwort gestoßen, da ich zum selben Thema recherchiere. Ich versuche, die Betriebssystemverbesserungen zu verstehen, die nio den Java-Programmierern bietet. Einige davon sind - DMA- und speicherabgebildete Dateien. Sind Sie auf weitere solche Verbesserungen gestoßen? PS - Deine Blog-Links sind kaputt.
Andy Dufresne
@AndyDufresne Mein Blog ist momentan nicht verfügbar und wird später in dieser Woche veröffentlicht.
Stu Thompson
1
Wie wäre es einfach mit dem Kopieren von Dateien von einem Verzeichnis in ein anderes? (Jedes andere Laufwerk)
Deckard
1
Interessant: mailinator.blogspot.in/2008/02/…
Jeryl Cook
38

Wenn Sie die Leistung des Dateikopierens vergleichen möchten, sollten Sie für den Kanaltest stattdessen Folgendes tun:

final FileInputStream inputStream = new FileInputStream(src);
final FileOutputStream outputStream = new FileOutputStream(dest);
final FileChannel inChannel = inputStream.getChannel();
final FileChannel outChannel = outputStream.getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
inputStream.close();
outputStream.close();

Dies ist nicht langsamer als das Puffern von einem Kanal zum anderen und möglicherweise massiv schneller. Nach Angaben der Javadocs:

Viele Betriebssysteme können Bytes direkt vom Dateisystem-Cache auf den Zielkanal übertragen, ohne sie tatsächlich zu kopieren.

uckelman
quelle
7

Basierend auf meinen Tests (Win7 64-Bit, 6 GB RAM, Java6) ist NIO transferFrom nur bei kleinen Dateien schnell und wird bei größeren Dateien sehr langsam. Der NIO-Datenpuffer-Flip übertrifft immer die Standard-E / A.

  • Kopieren von 1000x2MB

    1. NIO (transferFrom) ~ 2300 ms
    2. NIO (Direct Databuffer 5000b Flip) ~ 3500 ms
    3. Standard-E / A (Puffer 5000b) ~ 6000 ms
  • Kopieren von 100x20mb

    1. NIO (Direct Databuffer 5000b Flip) ~ 4000ms
    2. NIO (transferFrom) ~ 5000 ms
    3. Standard-E / A (Puffer 5000b) ~ 6500 ms
  • Kopieren von 1x1000mb

    1. NIO (Direct Databuffer 5000b Flip) ~ 4500s
    2. Standard-E / A (Puffer 5000b) ~ 7000 ms
    3. NIO (transferFrom) ~ 8000 ms

Die transferTo () -Methode arbeitet mit Blöcken einer Datei. war nicht als allgemeine Dateikopiermethode gedacht: Wie kopiere ich eine große Datei in Windows XP?

Erkki Nokso-Koivisto
quelle
6

Beantwortung des Teils "Nützlichkeit" der Frage:

Ein ziemlich subtiles Problem bei der Verwendung von FileChannelover FileOutputStreamist, dass das Ausführen einer seiner Blockierungsoperationen (z. B. read()oder write()) von einem Thread, der sich in einem unterbrochenen Zustand befindet, dazu führt, dass der Kanal abrupt geschlossen wird java.nio.channels.ClosedByInterruptException.

Dies könnte eine gute Sache sein, wenn alles, wofür das verwendet FileChannelwurde, Teil der Hauptfunktion des Threads ist und das Design dies berücksichtigt.

Es kann aber auch lästig sein, wenn es von einer Zusatzfunktion wie einer Protokollierungsfunktion verwendet wird. Beispielsweise wird Ihre Protokollierungsausgabe plötzlich geschlossen, wenn die Protokollierungsfunktion zufällig von einem Thread aufgerufen wird, der ebenfalls unterbrochen ist.

Es ist bedauerlich, dass dies so subtil ist, da die Nichtberücksichtigung dies zu Fehlern führen kann, die die Schreibintegrität beeinträchtigen. [1] [2]

Antak
quelle
3

Ich habe die Leistung von FileInputStream vs. FileChannel zum Decodieren von Base64-codierten Dateien getestet. In meinen Erfahrungen habe ich ziemlich große Dateien getestet und traditionelles io war immer etwas schneller als nio.

FileChannel hatte in früheren Versionen des JVM möglicherweise einen Vorteil aufgrund des Synchronisierungsaufwands in mehreren io-verwandten Klassen, aber moderne JVM sind ziemlich gut darin, nicht benötigte Sperren zu entfernen.

Jörn Horstmann
quelle
2

Wenn Sie die Funktion transferTo oder nicht blockierende Funktionen nicht verwenden, werden Sie keinen Unterschied zwischen herkömmlichem E / A und NIO (2) feststellen, da das herkömmliche E / A NIO zugeordnet ist.

Wenn Sie jedoch die NIO-Funktionen wie transferFrom / To verwenden können oder Puffer verwenden möchten, ist NIO natürlich der richtige Weg.

eckes
quelle
0

Ich habe die Erfahrung gemacht, dass NIO mit kleinen Dateien viel schneller ist. Bei großen Dateien ist FileInputStream / FileOutputStream jedoch viel schneller.

Tangens
quelle
4
Hast du das durcheinander gebracht? Meine eigene Erfahrung ist, dass dies java.niobei größeren Dateien schneller ist als bei java.iokleineren.
Stu Thompson
Nein, meine Erfahrung ist umgekehrt. java.nioist schnell, solange die Datei klein genug ist, um dem Speicher zugeordnet zu werden. Wenn es größer wird (200 MB und mehr), java.ioist es schneller.
Tangens
Beeindruckend. Das totale Gegenteil von mir. Beachten Sie, dass Sie nicht unbedingt eine Datei zuordnen müssen, um sie zu lesen - man kann aus der lesen FileChannel.read(). Es gibt nicht nur einen einzigen Ansatz zum Lesen von Dateien java.nio.
Stu Thompson
2
@tangens hast du das überprüft?
Sinedsem