2010-04-07 7 views
4

J'essaie d'illustrer la différence de performance entre les E/S traditionnelles et les fichiers mappés en mémoire dans Java pour les étudiants. J'ai trouvé un exemple quelque part sur internet mais tout n'est pas clair pour moi, je ne pense même pas que toutes les étapes soient de nature. Je lis beaucoup à ce sujet ici et là, mais je ne suis pas convaincu d'une mise en œuvre correcte de l'un ni de l'autre.IO traditionnel vs mappé en mémoire

Le code que j'essaie de comprendre est:

public class FileCopy{ 
    public static void main(String args[]){ 
     if (args.length < 1){ 
      System.out.println(" Wrong usage!"); 
      System.out.println(" Correct usage is : java FileCopy <large file with full path>"); 
      System.exit(0); 
     } 


     String inFileName = args[0]; 
     File inFile = new File(inFileName); 

     if (inFile.exists() != true){ 
      System.out.println(inFileName + " does not exist!"); 
      System.exit(0); 
     } 

     try{ 
      new FileCopy().memoryMappedCopy(inFileName, inFileName+".new"); 
      new FileCopy().customBufferedCopy(inFileName, inFileName+".new1"); 
     }catch(FileNotFoundException fne){ 
      fne.printStackTrace(); 
     }catch(IOException ioe){ 
      ioe.printStackTrace(); 
     }catch (Exception e){ 
      e.printStackTrace(); 
     } 


    } 

    public void memoryMappedCopy(String fromFile, String toFile) throws Exception{ 
     long timeIn = new Date().getTime(); 
     // read input file 
     RandomAccessFile rafIn = new RandomAccessFile(fromFile, "rw"); 
     FileChannel fcIn = rafIn.getChannel(); 
     ByteBuffer byteBuffIn = fcIn.map(FileChannel.MapMode.READ_WRITE, 0,(int) fcIn.size()); 
     fcIn.read(byteBuffIn); 
     byteBuffIn.flip(); 

     RandomAccessFile rafOut = new RandomAccessFile(toFile, "rw"); 
     FileChannel fcOut = rafOut.getChannel(); 

     ByteBuffer writeMap = fcOut.map(FileChannel.MapMode.READ_WRITE,0,(int) fcIn.size()); 

     writeMap.put(byteBuffIn); 

     long timeOut = new Date().getTime(); 
     System.out.println("Memory mapped copy Time for a file of size :" + (int) fcIn.size() +" is "+(timeOut-timeIn)); 
     fcOut.close(); 
     fcIn.close(); 
    } 


    static final int CHUNK_SIZE = 100000; 
    static final char[] inChars = new char[CHUNK_SIZE]; 

    public static void customBufferedCopy(String fromFile, String toFile) throws IOException{ 
     long timeIn = new Date().getTime(); 

     Reader in = new FileReader(fromFile); 
     Writer out = new FileWriter(toFile); 
     while (true) { 
      synchronized (inChars) { 
       int amountRead = in.read(inChars); 
       if (amountRead == -1) { 
        break; 
       } 
       out.write(inChars, 0, amountRead); 
      } 
     } 
     long timeOut = new Date().getTime(); 
     System.out.println("Custom buffered copy Time for a file of size :" + (int) new File(fromFile).length() +" is "+(timeOut-timeIn)); 
     in.close(); 
     out.close(); 
    } 
} 

Quand exactement est-il nececary utiliser RandomAccessFile? Ici, il est utilisé pour lire et écrire dans le memoryMappedCopy, est-il vraiment nécessaire de simplement copier un fichier? Ou est-ce une partie de la cartographie des souvenirs? En customBufferedCopy, pourquoi synchronized est utilisé ici?

J'ai aussi trouvé un autre exemple que -should- tester les performances entre les 2:

public class MappedIO { 
    private static int numOfInts = 4000000; 
    private static int numOfUbuffInts = 200000; 
    private abstract static class Tester { 
     private String name; 
     public Tester(String name) { this.name = name; } 
     public long runTest() { 
      System.out.print(name + ": "); 
      try { 
       long startTime = System.currentTimeMillis(); 
       test(); 
       long endTime = System.currentTimeMillis(); 
       return (endTime - startTime); 
      } catch (IOException e) { 
       throw new RuntimeException(e); 
      } 
     } 
     public abstract void test() throws IOException; 
    } 
    private static Tester[] tests = { 
     new Tester("Stream Write") { 
      public void test() throws IOException { 
       DataOutputStream dos = new DataOutputStream(
         new BufferedOutputStream(
           new FileOutputStream(new File("temp.tmp")))); 
       for(int i = 0; i < numOfInts; i++) 
        dos.writeInt(i); 
       dos.close(); 
      } 
     }, 
     new Tester("Mapped Write") { 
      public void test() throws IOException { 
       FileChannel fc = 
        new RandomAccessFile("temp.tmp", "rw") 
       .getChannel(); 
       IntBuffer ib = fc.map(
         FileChannel.MapMode.READ_WRITE, 0, fc.size()) 
         .asIntBuffer(); 
       for(int i = 0; i < numOfInts; i++) 
        ib.put(i); 
       fc.close(); 
      } 
     }, 
     new Tester("Stream Read") { 
      public void test() throws IOException { 
       DataInputStream dis = new DataInputStream(
         new BufferedInputStream(
           new FileInputStream("temp.tmp"))); 
       for(int i = 0; i < numOfInts; i++) 
        dis.readInt(); 
       dis.close(); 
      } 
     }, 
     new Tester("Mapped Read") { 
      public void test() throws IOException { 
       FileChannel fc = new FileInputStream(
         new File("temp.tmp")).getChannel(); 
       IntBuffer ib = fc.map(
         FileChannel.MapMode.READ_ONLY, 0, fc.size()) 
         .asIntBuffer(); 
       while(ib.hasRemaining()) 
        ib.get(); 
       fc.close(); 
      } 
     }, 
     new Tester("Stream Read/Write") { 
      public void test() throws IOException { 
       RandomAccessFile raf = new RandomAccessFile(
         new File("temp.tmp"), "rw"); 
       raf.writeInt(1); 
       for(int i = 0; i < numOfUbuffInts; i++) { 
        raf.seek(raf.length() - 4); 
        raf.writeInt(raf.readInt()); 
       } 
       raf.close(); 
      } 
     }, 
     new Tester("Mapped Read/Write") { 
      public void test() throws IOException { 
       FileChannel fc = new RandomAccessFile(
         new File("temp.tmp"), "rw").getChannel(); 
       IntBuffer ib = fc.map(
         FileChannel.MapMode.READ_WRITE, 0, fc.size()) 
         .asIntBuffer(); 
       ib.put(0); 
       for(int i = 1; i < numOfUbuffInts; i++) 
        ib.put(ib.get(i - 1)); 
       fc.close(); 
      } 
     } 
    }; 
    public static void main(String[] args) { 
     for(int i = 0; i < tests.length; i++) 
      System.out.println(tests[i].runTest()); 
    } 
} 

Je vois plus ou moins ce qui se passe, ma sortie ressemble à ceci:

Stream Write: 653 
Mapped Write: 51 
Stream Read: 651 
Mapped Read: 40 
Stream Read/Write: 14481 
Mapped Read/Write: 6 

Qu'est-ce qui rend le Stream Read/Write si incroyablement long? Et comme un test de lecture/écriture, pour moi, il semble un peu inutile de lire le même entier encore et encore (si je comprends bien ce qui se passe dans le Stream Read/Write) Ne serait-il pas mieux de lire int à partir du fichier précédemment écrit et juste lire et écrire ints sur le même endroit? Y a-t-il une meilleure façon de l'illustrer?

J'ai me casser la tête sur beaucoup de ces choses pendant un certain temps et je ne peux pas obtenir toute l'image ..

Répondre

0

1) Ces sons comme des questions à vos élèves devraient se poser - pas inverse?

2) La raison pour laquelle les deux méthodes sont utilisées est de démontrer les différentes façons de copier un fichier. Je devrais hasarder une supposition que la première méthode (RamdomAccessFile) crée une version du fichier dans la mémoire RAM, puis copie vers une nouvelle version sur le disque, et que la deuxième méthode (customBufferedCop) lit directement à partir du lecteur.

3) Je ne suis pas sûr, mais je pense que synchronized est utilisé pour s'assurer que plusieurs instances de la même classe n'écrivent pas en même temps.

4) En ce qui concerne la dernière question, je dois y aller - alors j'espère que quelqu'un d'autre peut vous aider avec ça. Sérieusement, cependant, cela ressemble aux questions qu'un tuteur devrait enseigner à ses étudiants. Si vous n'avez pas la possibilité de faire des recherches simples sur ce genre de choses, quel genre d'exemple donnez-vous à vos élèves? </diatribe >

+1

Seidre: Quelle meilleure façon de comprendre que de demander de l'aide? – Geoff

+0

Je n'ai pas très bien expliqué ma situation (je pensais que cela n'avait pas vraiment d'importance, une question est une question, non?) Je fais mon stage pour un professeur en systèmes d'information. Pour l'OS sujet, il a décidé de démontrer quelques principes en Java. N'ayant pas de vrai background java lui-même (il en sait encore beaucoup) mais n'a pas le temps d'expérimenter. C'est à moi d'améliorer/expliquer ses exemples. En tant qu'étudiant, j'ai encore beaucoup à apprendre et je ne comprends pas encore tout. C'est pourquoi je le demande ici .. Merci d'avoir essayé malgré tout. La classe n'est pas multithread, donc je ne vois pas pourquoi synchronized est utilisé? – Senne

+0

Il n'y a aucune raison d'avoir une synchronisation à moins que l'auteur ait également utilisé cette méthode d'une autre classe où il testait le multithreading. Mais il y a beaucoup de problèmes avec cela - la méthode ne devrait pas être statique et le tampon non-statique. Le tampon doit être défini dans la méthode elle-même pour ce test. J'ai mis à jour ma réponse. –

2

Ce que je vois avec une référence « flux de lecture/écriture » est:

  • Il ne fait pas vraiment flux d'E/S, mais cherche à un emplacement spécifique dans le fichier. Ceci est non-bufferisé donc toutes les E/S doivent être complétées à partir du disque (les autres flux utilisent des E/S tamponnées donc vraiment lire/écrire en gros blocs puis les ints sont lus ou écrits dans la zone mémoire).
  • Il cherche à la fin - 4 octets donc lit le dernier int et écrit un nouvel int.Le fichier continue de croître en longueur d'un int chaque itération. Cela n'ajoute pas grand chose au coût du temps (mais montre que l'auteur de cette référence a mal compris ou n'a pas fait attention).

Ceci explique le coût très élevé de ce cas-test particulier.

Vous avez demandé:

Ne serait-il préférable de lire le fichier écrit précédemment de int et seulement lire et écrire ints sur la même place?

C'est ce que l'auteur, je pense, essayait de faire avec les deux derniers benchmarks, mais ce n'est pas ce qu'ils ont obtenu. Avec RandomAccessFile à lire et à écrire au même endroit dans le fichier que vous avez besoin de mettre recherche avant la lecture et l'écriture:

raf.seek(raf.length() - 4); 
int val = raf.readInt(); 
raf.seek(raf.length() - 4); 
raf.writeInt(val); 

cela ne démontre un avantage de la mémoire mappée E/S puisque vous ne pouvez utiliser que même adresse de mémoire pour accéder aux mêmes bits du fichier au lieu d'avoir à faire une recherche supplémentaire avant chaque appel. Par ailleurs, votre premier exemple de classe de référence peut également présenter des problèmes car CHUNK_SIZE n'est pas un multiple pair de la taille de bloc du système de fichiers. Souvent, il est bon d'utiliser des multiples de 1024 et 8192 comme un bon point d'accès pour la plupart des applications (et la raison pour laquelle les Java BufferedInputStream et BufferedOutputStream utilisent cette valeur pour les tailles de tampons par défaut). Le système d'exploitation devra lire un ou plusieurs blocs supplémentaires pour satisfaire les demandes de lecture qui ne se trouvent pas sur les limites du bloc. Les lectures suivantes (d'un flux) reliront le même bloc, éventuellement des blocs entiers, puis un extra. Les E/S mappées en mémoire lisent et écrivent toujours physiquement en blocs, car les E/S réelles sont gérées par le gestionnaire de mémoire du SE qui utiliserait la taille de la page. La taille de la page est toujours optimisée pour bien correspondre aux blocs de fichiers.

Dans cet exemple, le test de mappage en mémoire lit tout dans un tampon de mémoire, puis réécrit tout le tout. Ces deux tests ne sont vraiment pas bien écrits pour comparer ces deux cas. memmoryMappedCopy doit lire et écrire dans la même taille de bloc que customBufferedCopy.

EDIT: Il peut même y avoir plus de problèmes avec ces classes de test. En raison de votre commentaire à l'autre réponse, j'ai regardé de nouveau plus attentivement le premier cours. La méthode customBufferedCopy est statique et utilise un tampon statique. Pour ce type de test, ce tampon doit être défini dans la méthode. Ensuite, il n'aurait pas besoin d'utiliser synchronized (bien qu'il n'en ait pas besoin dans ce contexte et pour ces tests de toute façon). Cette méthode statique est appelée comme une méthode normale, ce qui est une mauvaise pratique de programmation (à savoir utiliser FileCopy.customBufferedCopy(...) au lieu de new FileCopy().customBufferedCopy(...)). Si vous avez réellement exécuté ce test à partir de plusieurs threads, l'utilisation de ce tampon serait controversée et le benchmark ne concernerait pas uniquement les E/S de fichiers, il ne serait donc pas juste de comparer les résultats des deux méthodes de test.

0

Merci de votre recherche. Je vais regarder les premiers exemples plus tard, pour l'instant, mon professeur a demandé de réécrire les 2 tests (Stream et mappé lecture/écriture)
Ils génèrent des ints aléatoires, d'abord lire l'index (l'int généré) et vérifier si l'int à cet index est égal à l'int généré, s'il n'est pas égal, l'entier généré est écrit à son index.Il a pensé que cela pourrait aboutir à un meilleur test, en utilisant davantage le RandomAccessFile, est-ce que cela fait du sens?

Cependant, j'ai quelques problèmes, tout d'abord je ne sais pas comment utiliser un tampon avec le flux lire/écrire quand j'utilise RandomAccessFile, j'ai trouvé beaucoup sur byte[] tampons en utilisant un tableau mais je ne suis pas sûr comment l'utiliser correctement.
Mon code à ce jour pour ce test:

new Tester("Stream Read/Write") { 
     public void test() throws IOException { 
      RandomAccessFile raf = new RandomAccessFile(new File("temp.tmp"), "rw"); 
      raf.seek(numOfUbuffInts*4); 
      raf.writeInt(numOfUbuffInts); 
      for (int i = 0; i < numOfUbuffInts; i++) { 
       int getal = (int) (1 + Math.random() * numOfUbuffInts); 
       raf.seek(getal*4); 
       if (raf.readInt() != getal) { 
        raf.seek(getal*4); 
        raf.writeInt(getal); 
       } 
      } 
      raf.close(); 
     } 
    }, 

C'est donc encore unbuffered ..

Le deuxième test je l'ai fait comme suit:

new Tester("Mapped Read/Write") { 
     public void test() throws IOException { 
      RandomAccessFile raf = new RandomAccessFile(new File("temp.tmp"), "rw"); 
      raf.seek(numOfUbuffInts*4); 
      raf.writeInt(numOfUbuffInts); 
      FileChannel fc = raf.getChannel(); 
      IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size()).asIntBuffer(); 

      for(int i = 1; i < numOfUbuffInts; i++) { 
       int getal = (int) (1 + Math.random() * numOfUbuffInts); 
       if (ib.get(getal) != getal) { 
        ib.put(getal, getal); 
       } 
      } 
      fc.close(); 
     } 
    } 

Pour un petit nombre de numOfUbuffInts, il semble pour aller vite, pour les grands nombres (20 000 000+) cela prend des âges. J'ai juste essayé certaines choses mais je ne suis pas sûr si je suis sur la bonne voie.

Questions connexes