Question

J'essaie de déterminer s'il existe une différence de performances (ou d'avantages) lorsque nous utilisons nio FileChannel par rapport à FileInputStream / FileOuputStream normal pour lire et écrire des fichiers dans système de fichiers. J'ai constaté que, sur ma machine, les deux fonctionnaient au même niveau et plusieurs fois, la méthode FileChannel était plus lente. Puis-je s'il vous plaît connaître plus de détails comparant ces deux méthodes. Voici le code que j'ai utilisé, le fichier que je teste est autour de 350 Mo . Est-ce une bonne option d’utiliser des classes basées sur NIO pour les E / S sur fichier, si je ne cherche pas un accès aléatoire ou d’autres fonctionnalités avancées?

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");
    }
}
Était-ce utile?

La solution

D'après mon expérience avec des fichiers de plus grande taille, java.nio est plus rapide que java.io . Complètement plus rapide. Comme dans la plage de > 250%. Cela dit, j'élimine les goulots d'étranglement évidents, ce dont votre micro-référence pourrait souffrir. Domaines potentiels d’enquête:

La taille de la mémoire tampon. L'algorithme que vous avez en principe est

.
  • copier du disque vers le tampon
  • copier du tampon sur le disque

Selon ma propre expérience, la taille de cette mémoire tampon est mûr , à accorder. J'ai choisi 4 Ko pour une partie de mon application et 256 Ko pour une autre. Je soupçonne que votre code souffre d'un tampon aussi volumineux. Exécuter des 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'exécutez pas de tests comparatifs Java lisant et écrivant sur le même disque.

Si vous le faites, vous comparez réellement le disque et non Java. Je suggérerais également que si votre processeur n'est pas occupé, vous rencontrez probablement un autre goulot d'étranglement.

N'utilisez pas de tampon si vous n'en avez pas besoin.

Pourquoi copier en mémoire si votre cible est un autre disque ou une carte réseau? Avec des fichiers plus volumineux, la latence induite est non négligeable.

Comme d'autres l'ont déjà dit, utilisez FileChannel.transferTo () ou FileChannel.transferFrom () . L’avantage principal ici est que la machine virtuelle utilise l’accès du système d’exploitation à DMA ( Accès direct à la mémoire ), si présent. (Cela dépend de l'implémentation, mais les versions modernes de Sun et IBM sur les processeurs à usage général sont bonnes à utiliser.) Les données vont directement au disque, au bus, puis à la destination. ... en contournant tous les circuits via la RAM ou le processeur.

L’application Web sur laquelle je passe mes journées et mes nuits est très lourde. J'ai également réalisé des micro-tests et des tests dans le monde réel. Et les résultats sont en hausse sur mon blog, jetez un coup d'œil:

Utiliser les données et les environnements de production

Les micro-repères sont sujets à la distorsion. Si vous le pouvez, efforcez-vous de rassembler des données correspondant exactement à ce que vous envisagez de faire, avec la charge que vous attendez, sur le matériel que vous attendez.

Mes critères de référence sont solides et fiables car ils se sont déroulés sur un système de production, un système robuste, un système sous charge rassemblé dans des journaux. Pas les 7200 tr / min de mon ordinateur portable 2.5 " SATA alors que je regardais intensément la machine virtuelle travailler sur mon disque dur.

Sur quoi courez-vous? C'est important.

Autres conseils

Si vous souhaitez comparer les performances de la copie de fichiers, procédez comme suit pour le test de canal:

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

Cela ne sera pas plus lent que la mise en mémoire tampon d’un canal à l’autre et sera potentiellement beaucoup 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.

Sur la base de mes tests (Win7 64 bits, 6 Go de RAM, Java6), NIO transferFrom est rapide uniquement avec les petits fichiers et devient très lent pour les fichiers plus volumineux. Le retournement du tampon de données NIO surpasse toujours les E / S standard.

  • Copie 1 000 x 2 Mo

    1. NIO (transferFrom) ~ 2300ms
    2. NIO (flip de datababuffer direct 5000b) ~ 3500ms
    3. E / S standard (tampon 5000b) ~ 6000 ms
  • Copie 100 x 20 Mo

    1. NIO (tampon de données direct 5000b flip) ~ 4000 ms
    2. NIO (transferFrom) ~ 5000ms
    3. E / S standard (tampon 5000b) ~ 6500ms
  • Copie 1x1000mb

    1. NIO (basculement direct du tampon de données 5000b) ~ 4500s
    2. E / S standard (tampon 5000b) ~ 7000 ms
    3. NIO (transferFrom) ~ 8000ms

La méthode transferTo () fonctionne sur les morceaux d'un fichier; n'était pas destiné à être une méthode de copie de fichier de haut niveau: Comment copier un fichier volumineux dans Windows XP?

Répondre à "l'utilité" une partie de la question:

L’utilisation FileChannel sur FileOutputStream est assez subtile si vous exécutez l’une de ses opérations de blocage (par exemple, read () ou write () ) à partir d'un fil qui se trouve dans l'état interrompu provoquera la fermeture brutale du canal avec java.nio.channels.ClosedByInterruptException .

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

Mais cela pourrait aussi ê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 cela soit si subtil, car ne pas en tenir compte peut conduire à des bogues affectant l'intégrité en écriture. [1] [2]

J'ai testé les performances de FileInputStream par rapport à FileChannel pour le décodage de fichiers codés en base64. Lors de mes expériences, j’ai testé un fichier assez volumineux et le io traditionnel était toujours un peu plus rapide que nio.

FileChannel aurait pu avoir un avantage dans les versions précédentes de jvm en raison de la surcharge de synchronisation liée à plusieurs classes liées à io, mais les jvm modernes sont assez efficaces pour supprimer les verrous inutiles.

Si vous n'utilisez pas la fonctionnalité transferTo ou des fonctionnalités non bloquantes, vous ne remarquerez aucune différence entre l'IO traditionnel et NIO (2), car l'IO traditionnel est mappé sur NIO.

Mais si vous pouvez utiliser les fonctionnalités de NIO telles que transferFrom / To ou si vous souhaitez utiliser des tampons, alors bien sûr, NIO est la voie à suivre.

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top