2009-10-22 15 views
150

Ich versuche herauszufinden, ob es einen Unterschied in der Leistung (oder Vorteile) gibt, wenn wir nio FileChannel gegenüber normalen FileInputStream/FileOuputStream verwenden, um Dateien in Dateisystem zu lesen und zu schreiben. Ich habe beobachtet, dass auf meiner Maschine beide auf dem gleichen Niveau, auch oft die FileChannel Weg ist langsamer. Kann ich bitte mehr Details im Vergleich dieser beiden Methoden wissen. Hier ist der Code, den ich verwendet habe, die Datei, mit der ich versuche, ist um 350MB. Ist es eine gute Option, NIO-basierte Klassen für Datei-I/O zu verwenden, wenn ich nicht auf den wahlfreien Zugriff oder andere solche erweiterten Funktionen schaue?Java NIO FileChannel versus FileOutputstream Leistung/Nützlichkeit

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"); 
    } 
} 
+4

'transferTo' /' transferFrom' würde für das Kopieren von Dateien konventionelleren sein. Welche Technik auch immer Ihre Festplatte nicht schneller oder langsamer machen sollte, obwohl ich denke, dass es ein Problem sein könnte, wenn es kleine Stücke gleichzeitig liest und den Kopf dazu veranlasst, eine übermäßige Menge an Zeit zu verbringen. –

+1

(Sie erwähnen nicht, welches Betriebssystem Sie verwenden oder welchen JRE-Hersteller und welche JRE-Version.) –

+0

Hoppla, ich verwende FC10 mit Sun JDK6. – Keshav

Antwort

183

Meine Erfahrung mit größeren Dateien hat gewesen, dass java.nio ist schneller als java.io. Solid schneller. Wie im> 250% Bereich. Nichtsdestotrotz beseitige ich offensichtliche Engpässe, von denen ich denke, dass Ihr Mikro-Benchmark darunter leiden könnte. Mögliche Untersuchungsgebiete:

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

  • Kopie von der Festplatte
  • Kopie von dem Puffer auf der Festplatte zu puffern

Meine eigene Erfahrung ist, dass diese Puffergröße ist reif für Tuning. Ich habe 4KB für einen Teil meiner Anwendung, 256KB für einen anderen festgelegt. Ich vermute, dass dein Code unter einem so großen Puffer leidet. 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 es zu beweisen.

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

Wenn Sie dies tun, dann benchmarken Sie wirklich die Festplatte und nicht Java. Ich würde auch vorschlagen, dass, wenn Ihre CPU nicht beschäftigt ist, Sie wahrscheinlich einen anderen Engpass erleben.

Verwenden Sie keinen Puffer, wenn Sie dies nicht benötigen.

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

Wie auch andere gesagt haben, verwenden Sie FileChannel.transferTo() oder FileChannel.transferFrom(). Der entscheidende Vorteil besteht darin, 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 auf Allzweck-CPUs sind gut zu gehen.) Was passiert, ist, dass die Daten direkt von/zur Disc, zum Bus und dann zum Ziel ... gehen, wobei sie alle umgehen Schaltung durch RAM oder die CPU.

Die Web-App, in der ich meine Tage und Nächte verbracht habe, ist sehr IO schwer. Ich habe auch Mikrobenchmarks und reale Benchmarks gemacht. Und die Ergebnisse sind auf meinem Blog, haben einen Blick gesehen haben:

Verwenden Produktionsdaten und Umgebungen

Micro-Benchmarks sind anfällig für Verzerrungen . Wenn Sie können, versuchen Sie, Daten von genau dem, was Sie vorhaben, mit der erwarteten Last auf der erwarteten Hardware zu sammeln.

Meine Benchmarks sind solide und zuverlässig, weil sie auf einem Produktionssystem, einem bulligen System, einem System unter Last, gesammelt in Holzscheiten stattfanden. Nicht mein Notebook 7200 RPM 2.5" Laufwerk SATA während ich intensiv wie die JVM arbeiten, um meine Festplatte

beobachtet. Was Sie laufen? Es ist wichtig.

+0

@Stu Thompson - Vielen Dank für Ihren Beitrag. Ich bin auf deine Antwort gestoßen, weil ich über das gleiche Thema recherchiere. Ich versuche die Verbesserungen des Betriebssystems zu verstehen, die nio den Java-Programmierern bietet. Ein paar von ihnen sind - DMA und Memory-Mapped-Dateien. Sind Sie auf solche Verbesserungen gestoßen? P.S - Ihre Blog-Links sind kaputt. –

+0

@AndyDufresne mein Blog ist im Moment down, wird später in dieser Woche - im Prozess des Verschiebens. –

+13

Hier sind die Blog-Links auf archive.org: http://web.archive.org/web/20120815094827/http://geekomatic.ch/2008/09.html http: //web.archive. org/web/20120821114802/http: //geekomatic.ch/2009/01.html –

-1

Meine Erfahrung ist, dass NIO viel schneller mit kleinen Dateien ist. Aber wenn es um große Dateien geht, ist FileInputStream/FileOutputStream viel schneller.

+4

Hast du das durcheinander gebracht? Meine eigene Erfahrung ist, dass 'java.nio' schneller ist mit * größeren * Dateien als' java.io', nicht kleiner. –

+0

Nein, meine Erfahrung ist umgekehrt. 'java.nio' ist schnell, solange die Datei klein genug ist, um dem Speicher zugeordnet zu werden. Wenn es größer wird (200 MB und mehr), ist 'java.io' schneller. – tangens

+0

Wow. Das totale Gegenteil von mir. Beachten Sie, dass Sie nicht unbedingt eine Datei zuordnen müssen, um sie zu lesen - man kann aus dem 'FileChannel.read()' lesen. Es gibt nicht nur einen einzigen Ansatz zum Lesen von Dateien mit 'java.nio'. –

32

Wenn das, was Sie vergleichen wollen Leistung von Kopieren von Dateien ist, dann für den Kanal Test sollten Sie dies stattdessen 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 wird nicht langsamer als sich auf die andere von einem Kanal Pufferung und wird möglicherweise massiv schneller sein. Entsprechend den Javadocs:

Viele Betriebssysteme können Bytes direkt vom Dateisystemcache zum Zielkanal übertragen, ohne sie wirklich zu kopieren.

3

Ich habe die Leistung von FileInputStream vs. FileChannel für die Decodierung von Base64-codierten Dateien getestet. In meinen Experimenten testete ich ziemlich große Datei und traditionelle io war immer ein bisschen schneller als nio.

FileChannel könnte in früheren Versionen des jvm einen Vorteil gehabt haben, weil es in mehreren verwandten Klassen einen Synchronisierungsaufwand verursacht, aber moderne jvm sind ziemlich gut darin, nicht benötigte Sperren zu entfernen.

7

Basierend auf meinen Tests (Win7 64bit, 6 GB RAM , Java6), ist NIO transferFrom nur bei kleinen Dateien schnell und bei großen Dateien sehr langsam.NIO-Datenpuffer-Flip übertrifft immer Standard-IO.

  • Kopieren 1000x2MB

    1. NIO (transferFrom) ~ 2300ms
    2. NIO (direkte datababuffer 5000B Flip) ~ 3500ms
    3. Standard-IO (Puffer 5000B) ~ 6000ms
  • Kopieren 100x20mb

    1. NIO (direkte datababuffer 5000B Flip) ~ 4000ms
    2. NIO (transferFrom) ~ 5000ms
    3. Standard-IO (Puffer 5000B) ~ 6500ms
  • Kopieren 1x1000mb

    1. NIO (direkte Datenbuffer 5000b flip) ~ 4500s
    2. Standard IO (Puffer 5000b) ~ 7000ms
    3. NIO (transferFrom) ~ 8000ms

Die transferTo() -Methode auf Chunks einer Datei arbeitet; wurde nicht als High-Level-Dateikopiermethode bestimmt: How to copy a large file in Windows XP?

2

Wenn Sie nicht mit der transferTo Funktion sind oder nicht-blockierenden Eigenschaften, die Sie keinen Unterschied zwischen traditionellen IO und NIO (2), da die traditionelle IO bemerken Karten zu NIO.

Aber wenn Sie die NIO-Funktionen wie TransferFrom/To verwenden können oder Puffer verwenden möchten, dann ist natürlich NIO der Weg zu gehen.

0

die „Nützlichkeit“ Teil der Frage zu beantworten:

Ein eher subtil Gotcha von FileChannel über FileOutputStream ist, dass von einem Thread jeder seiner Sperroperationen (zB read() oder write()) durchführen, die das bewirkt, dass in interrupted state ist Kanal, um abrupt mit java.nio.channels.ClosedByInterruptException zu schließen.

Nun könnte dies eine gute Sache sein, wenn was auch immer die FileChannel für verwendet wurde, ist Teil der Hauptfunktion des Threads, und Design berücksichtigt dies.

Es könnte aber auch nervtötend sein, wenn es von einer Hilfsfunktion wie einer Protokollierungsfunktion verwendet wird. Zum Beispiel können Sie feststellen, dass Ihre Logging-Ausgabe plötzlich geschlossen ist, wenn die Logging-Funktion zufällig von einem Thread aufgerufen wird, der ebenfalls unterbrochen ist.

Es ist bedauerlich, dass dies so subtil ist, weil dies nicht zu Fehlern führen kann, die sich auf die Schreibintegrität auswirken.[1] [2]