2009-10-22 3 views
150

J'essaie de comprendre s'il y a une différence de performance (ou avantages) lorsque nous utilisons nio FileChannel par rapport à la normale FileInputStream/FileOuputStream pour lire et écrire des fichiers sur le système de fichiers. J'ai observé que sur ma machine les deux fonctionnent au même niveau, aussi plusieurs fois le FileChannel est plus lent. Puis-je connaître plus de détails en comparant ces deux méthodes. Voici le code que j'ai utilisé, le fichier que je suis en train de tester est autour de 350MB. Est-ce une bonne option d'utiliser des classes basées sur NIO pour les E/S de fichiers, si je ne regarde pas l'accès aléatoire ou d'autres fonctionnalités avancées?Java NIO FileChannel par rapport à FileOutputstream performance/utilité

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' serait plus classique pour copier des fichiers. Quelle que soit la technique ne devrait pas rendre votre disque dur plus rapide ou plus lent, même si je suppose qu'il pourrait y avoir un problème si elle lit de petits morceaux à la fois et provoque la tête de passer un temps excessif à chercher. –

+1

(Vous ne mentionnez pas le système d'exploitation que vous utilisez ou le fournisseur et la version de JRE.) –

+0

Désolé, j'utilise FC10 avec Sun JDK6. – Keshav

Répondre

183

Mon expérience avec des fichiers de plus grande taille a été que java.nio est plus rapide que java.io. Solidement plus rapide. Comme dans la gamme> 250%. Cela dit, j'élimine les goulets d'étranglement évidents, dont je pense que votre micro-benchmark pourrait souffrir. Zones potentielles pour l'investigation:

La taille du tampon. L'algorithme que vous avez essentiellement est

  • copie du disque tampon
  • copie du tampon sur le disque

Ma propre expérience a été que cette taille de la mémoire tampon est mûre pour le réglage. J'ai réglé 4KB pour une partie de ma demande, 256KB pour une autre. Je soupçonne que votre code souffre avec un tel tampon. Exécutez quelques tests avec des tampons de 1 Ko, 2 Ko, 4 Ko, 8 Ko, 16 Ko, 32 Ko et 64 Ko pour le prouver à vous-même. N'effectuez pas de tests java qui lisent et écrivent sur le même disque. Si vous le faites, vous faites vraiment référence au disque, et non à Java. Je suggère également que si votre CPU n'est pas occupé, alors vous rencontrez probablement un autre goulot d'étranglement.N'utilisez pas de tampon si vous n'en avez pas besoin. Pourquoi copier dans la mémoire si votre cible est un autre disque ou une carte réseau (NIC)? Avec des fichiers plus volumineux, la latence encourue est non triviale. Comme d'autres l'ont dit, utilisez FileChannel.transferTo() ou FileChannel.transferFrom(). L'avantage clé ici est que la JVM utilise l'accès du système d'exploitation au DMA (Direct Memory Access), s'il est présent. (Cela dépend de l'implémentation, mais les versions modernes de Sun et d'IBM sur les UC polyvalentes sont bonnes.) Que se passe-t-il si les données vont directement du disque au bus, puis à la destination ... sans passer par circuit à travers la RAM ou le CPU.

L'application web sur laquelle j'ai passé mes journées et mon travail de nuit est très lourde. J'ai aussi fait des micro-benchmarks et des benchmarks réels. Et les résultats sont sur mon blog, un coup d'oeil-voir:

Utiliser les données de production et les environnements

micro-benchmarks sont sujettes à des distorsions . Si vous le pouvez, faites l'effort de recueillir des données à partir de ce que vous prévoyez de faire, avec la charge que vous attendez, sur le matériel que vous attendez. Mes références sont solides et fiables car elles ont eu lieu sur un système de production, un système costaud, un système sous charge, assemblé en rondins. Pas mon de portable 7200 RPM 2.5" disque SATA pendant que je regardais intensément que la machine virtuelle Java fonctionne mon disque dur.

Qu'est-ce que vous utilisez sur? Il importe.

+0

@Stu Thompson - Merci pour votre message. Je suis tombé sur votre réponse depuis que je fais des recherches sur le même sujet. J'essaie de comprendre les améliorations du système d'exploitation que nio expose aux programmeurs Java. Un couple d'entre eux étant - DMA et fichiers mappés en mémoire. Avez-vous rencontré plus de telles améliorations? P.S - Les liens de votre blog sont cassés. –

+0

@AndyDufresne mon blog est en panne pour le moment, sera plus tard cette semaine - en train de le déplacer. –

+13

Voici les liens de blog sur 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

Mon expérience est, que NIO est beaucoup plus rapide avec de petits fichiers. Mais quand il s'agit de fichiers volumineux FileInputStream/FileOutputStream est beaucoup plus rapide.

+4

Avez-vous trouvé cela mélangé? Ma propre expérience est que 'java.nio' est plus rapide avec des fichiers * plus volumineux que' java.io', pas plus petit. –

+0

Non, mon expérience est inverse. 'java.nio' est rapide tant que le fichier est assez petit pour être mappé en mémoire. Si elle grossit (200 Mo et plus), java.io est plus rapide. – tangens

+0

Wow. Le total opposé de moi. Notez que vous n'avez pas forcément besoin de mapper un fichier pour le lire - vous pouvez lire 'FileChannel.read()'. Il n'y a pas qu'une seule approche pour lire les fichiers en utilisant 'java.nio'. –

32

Si la chose que vous voulez comparer est la performance de la copie de fichiers, puis pour le test de canal, vous devez le faire à la place:

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

Ce ne sera pas plus lent que vous tamponnage d'un canal à l'autre , et sera potentiellement massivement plus rapide. Selon les Javadocs:

De nombreux systèmes d'exploitation peuvent transférer des octets directement du cache du système de fichiers vers le canal cible sans les copier réellement.

3

J'ai testé les performances de FileInputStream par rapport à FileChannel pour décoder les fichiers encodés en base64. Dans mes expériences j'ai testé un fichier assez volumineux et le io traditionnel était toujours un peu plus rapide que le nio. FileChannel peut avoir eu un avantage dans les versions antérieures de jvm en raison de la surcharge de la synchonisation dans plusieurs classes liées à io, mais les versions modernes de jvm sont assez efficaces pour supprimer les verrous inutiles.

7

Sur la base de mes tests (Win7 64bits, 6 Go de RAM , Java6), NIO transferFrom est rapide seulement avec de petits fichiers et devient très lent sur des fichiers plus gros.Le flip de base de données NIO surpasse toujours les E/S standard.

  • Copie 1000x2MB

    1. NIO (transferFrom) ~ 2300ms
    2. NIO (FLIP de 5000B de datababuffer direct) ~ 3500ms
    3. standard IO (en 5000B tampon) ~ 6000ms
  • Copie 100x20mb

    1. NIO (FLIP de 5000B de datababuffer direct) ~ 4000ms
    2. NIO (transferFrom) ~ 5000ms
    3. standard IO (5000B tampon) ~ 6500ms
  • copie 1x1000mb

    1. NIO (directe datababuffer 5000b flip) ~ 4500s
    2. Standard IO (tampon 5000b) ~ 7000ms
    3. NIO (transferFrom) ~ 8000ms

La méthode transferTo() fonctionne sur des morceaux d'un fichier; n'a pas été conçu comme une méthode de copie de fichiers de haut niveau: How to copy a large file in Windows XP?

2

Si vous n'utilisez la fonction transferTo ou caractéristiques non-bloquant, vous ne remarquerez une différence entre IO et NIO traditionnelle (2) parce que l'IO traditionnelle cartes à NIO. Mais si vous pouvez utiliser les fonctions NIO comme transferFrom/To ou si vous voulez utiliser des Buffers, alors bien sur NIO est la solution.

0

Répondre à la « utilité » partie de la question:

Un Gotcha assez subtile d'utiliser FileChannel sur FileOutputStream est que l'exécution de ses opérations de blocage (par exemple read() ou write()) à partir d'un fil qui est en interrupted state provoquera la canal à fermer brusquement avec java.nio.channels.ClosedByInterruptException.

Maintenant, cela pourrait être une bonne chose si quelque soit le FileChannel utilisé fait partie de la fonction principale du thread, et la conception en a tenu compte.

Mais il pourrait également être embêtant s'il est utilisé par une fonction auxiliaire telle qu'une fonction de journalisation. Par exemple, vous pouvez trouver votre sortie de journalisation soudainement fermée si la fonction de journalisation est appelée par un thread également interrompu. Il est regrettable que ce soit si subtil, car ne pas en tenir compte peut conduire à des bogues qui affectent l'intégrité de l'écriture.[1] [2]

Questions connexes