Question

J'ai deux fichiers (2 Go chacun) sur mon disque dur et que vous souhaitez les comparer les uns aux autres:

  • Copie les fichiers d'origine avec l'explorateur de Windows prend environ. 2-4 minutes (qui est en train de lire et d'écrire - sur le même disque physique et logique).
  • Lecture avec java.io.FileInputStream deux fois et en comparant les tableaux d'octets sur un octet par octet base prend plus de 20 minutes.
  • tampon java.io.BufferedInputStream est 64ko, les fichiers sont lus en morceaux, puis comparés.
  • La comparaison est faite est une boucle serrée comme

    int numRead = Math.min(numRead[0], numRead[1]);
    for (int k = 0; k < numRead; k++)
    {
       if (buffer[1][k] != buffer[0][k])
       {
          return buffer[0][k] - buffer[1][k];
       }
    }
    

Qu'est-ce que je peux faire pour accélérer ce? NIO est censé être plus rapide que les flux simples? Java est incapable d'utiliser les technologies DMA / SATA et certains appels ne OS-API lent à la place?

EDIT: Merci pour les réponses. Je l'ai fait quelques expériences basées sur eux. Comme Andreas a montré

  

cours d'eau ou des approches nio ne diffèrent pas beaucoup.
  Le plus important est la taille de la mémoire tampon correcte.

Ceci est confirmé par mes propres expériences. Comme les fichiers sont lus en gros morceaux, même des tampons supplémentaires (BufferedInputStream) ne donnent rien. L'optimisation de la comparaison est possible et j'obtenu les meilleurs résultats avec 32 déroulage fois, mais les dépenses de temps en comparaison est faible par rapport à lecture de disque, de sorte que le petit est speedup. On dirait qu'il n'y a rien que je puisse faire, - (

Était-ce utile?

La solution

J'ai essayé trois méthodes différentes de comparaison de deux fichiers identiques gb avec 3,8 tailles de tampon entre 8 kb et 1 MB. le premier premier procédé utilise seulement deux flux d'entrée tamponnées

La seconde approche utilise un threadpool qui lit dans deux fils différents et compare dans un troisième. cela a un débit légèrement supérieur au détriment d'une utilisation élevée cpu. la gestion de l'threadpool prend beaucoup de frais généraux avec ces tâches à court en cours d'exécution.

la troisième approche utilise nio, telle que publiée par laginimaineb

comme vous pouvez le voir, l'approche générale ne diffère pas beaucoup. plus importante est la taille du tampon correct.

ce qui est étrange que je l'ai lu 1 octet de poids en utilisant des threads. je ne pouvais pas repérer l'erreur difficile.

comparing just with two streams
I was equal, even after 3684070360 bytes and reading for 704813 ms (4,98MB/sec * 2) with a buffer size of 8 kB
I was equal, even after 3684070360 bytes and reading for 578563 ms (6,07MB/sec * 2) with a buffer size of 16 kB
I was equal, even after 3684070360 bytes and reading for 515422 ms (6,82MB/sec * 2) with a buffer size of 32 kB
I was equal, even after 3684070360 bytes and reading for 534532 ms (6,57MB/sec * 2) with a buffer size of 64 kB
I was equal, even after 3684070360 bytes and reading for 422953 ms (8,31MB/sec * 2) with a buffer size of 128 kB
I was equal, even after 3684070360 bytes and reading for 793359 ms (4,43MB/sec * 2) with a buffer size of 256 kB
I was equal, even after 3684070360 bytes and reading for 746344 ms (4,71MB/sec * 2) with a buffer size of 512 kB
I was equal, even after 3684070360 bytes and reading for 669969 ms (5,24MB/sec * 2) with a buffer size of 1024 kB
comparing with threads
I was equal, even after 3684070359 bytes and reading for 602391 ms (5,83MB/sec * 2) with a buffer size of 8 kB
I was equal, even after 3684070359 bytes and reading for 523156 ms (6,72MB/sec * 2) with a buffer size of 16 kB
I was equal, even after 3684070359 bytes and reading for 527547 ms (6,66MB/sec * 2) with a buffer size of 32 kB
I was equal, even after 3684070359 bytes and reading for 276750 ms (12,69MB/sec * 2) with a buffer size of 64 kB
I was equal, even after 3684070359 bytes and reading for 493172 ms (7,12MB/sec * 2) with a buffer size of 128 kB
I was equal, even after 3684070359 bytes and reading for 696781 ms (5,04MB/sec * 2) with a buffer size of 256 kB
I was equal, even after 3684070359 bytes and reading for 727953 ms (4,83MB/sec * 2) with a buffer size of 512 kB
I was equal, even after 3684070359 bytes and reading for 741000 ms (4,74MB/sec * 2) with a buffer size of 1024 kB
comparing with nio
I was equal, even after 3684070360 bytes and reading for 661313 ms (5,31MB/sec * 2) with a buffer size of 8 kB
I was equal, even after 3684070360 bytes and reading for 656156 ms (5,35MB/sec * 2) with a buffer size of 16 kB
I was equal, even after 3684070360 bytes and reading for 491781 ms (7,14MB/sec * 2) with a buffer size of 32 kB
I was equal, even after 3684070360 bytes and reading for 317360 ms (11,07MB/sec * 2) with a buffer size of 64 kB
I was equal, even after 3684070360 bytes and reading for 643078 ms (5,46MB/sec * 2) with a buffer size of 128 kB
I was equal, even after 3684070360 bytes and reading for 865016 ms (4,06MB/sec * 2) with a buffer size of 256 kB
I was equal, even after 3684070360 bytes and reading for 716796 ms (4,90MB/sec * 2) with a buffer size of 512 kB
I was equal, even after 3684070360 bytes and reading for 652016 ms (5,39MB/sec * 2) with a buffer size of 1024 kB

le code utilisé:

import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.concurrent.*;

public class FileCompare {

    private static final int MIN_BUFFER_SIZE = 1024 * 8;
    private static final int MAX_BUFFER_SIZE = 1024 * 1024;
    private String fileName1;
    private String fileName2;
    private long start;
    private long totalbytes;

    @Before
    public void createInputStream() {
        fileName1 = "bigFile.1";
        fileName2 = "bigFile.2";
    }

    @Test
    public void compareTwoFiles() throws IOException {
        System.out.println("comparing just with two streams");
        int currentBufferSize = MIN_BUFFER_SIZE;
        while (currentBufferSize <= MAX_BUFFER_SIZE) {
            compareWithBufferSize(currentBufferSize);
            currentBufferSize *= 2;
        }
    }

    @Test
    public void compareTwoFilesFutures() 
            throws IOException, ExecutionException, InterruptedException {
        System.out.println("comparing with threads");
        int myBufferSize = MIN_BUFFER_SIZE;
        while (myBufferSize <= MAX_BUFFER_SIZE) {
            start = System.currentTimeMillis();
            totalbytes = 0;
            compareWithBufferSizeFutures(myBufferSize);
            myBufferSize *= 2;
        }
    }

    @Test
    public void compareTwoFilesNio() throws IOException {
        System.out.println("comparing with nio");
        int myBufferSize = MIN_BUFFER_SIZE;
        while (myBufferSize <= MAX_BUFFER_SIZE) {
            start = System.currentTimeMillis();
            totalbytes = 0;
            boolean wasEqual = isEqualsNio(myBufferSize);

            if (wasEqual) {
                printAfterEquals(myBufferSize);
            } else {
                Assert.fail("files were not equal");
            }

            myBufferSize *= 2;
        }

    }

    private void compareWithBufferSize(int myBufferSize) throws IOException {
        final BufferedInputStream inputStream1 =
                new BufferedInputStream(
                        new FileInputStream(new File(fileName1)),
                        myBufferSize);
        byte[] buff1 = new byte[myBufferSize];
        final BufferedInputStream inputStream2 =
                new BufferedInputStream(
                        new FileInputStream(new File(fileName2)),
                        myBufferSize);
        byte[] buff2 = new byte[myBufferSize];
        int read1;

        start = System.currentTimeMillis();
        totalbytes = 0;
        while ((read1 = inputStream1.read(buff1)) != -1) {
            totalbytes += read1;
            int read2 = inputStream2.read(buff2);
            if (read1 != read2) {
                break;
            }
            if (!Arrays.equals(buff1, buff2)) {
                break;
            }
        }
        if (read1 == -1) {
            printAfterEquals(myBufferSize);
        } else {
            Assert.fail("files were not equal");
        }
        inputStream1.close();
        inputStream2.close();
    }

    private void compareWithBufferSizeFutures(int myBufferSize)
            throws ExecutionException, InterruptedException, IOException {
        final BufferedInputStream inputStream1 =
                new BufferedInputStream(
                        new FileInputStream(
                                new File(fileName1)),
                        myBufferSize);
        final BufferedInputStream inputStream2 =
                new BufferedInputStream(
                        new FileInputStream(
                                new File(fileName2)),
                        myBufferSize);

        final boolean wasEqual = isEqualsParallel(myBufferSize, inputStream1, inputStream2);

        if (wasEqual) {
            printAfterEquals(myBufferSize);
        } else {
            Assert.fail("files were not equal");
        }
        inputStream1.close();
        inputStream2.close();
    }

    private boolean isEqualsParallel(int myBufferSize
            , final BufferedInputStream inputStream1
            , final BufferedInputStream inputStream2)
            throws InterruptedException, ExecutionException {
        final byte[] buff1Even = new byte[myBufferSize];
        final byte[] buff1Odd = new byte[myBufferSize];
        final byte[] buff2Even = new byte[myBufferSize];
        final byte[] buff2Odd = new byte[myBufferSize];
        final Callable<Integer> read1Even = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream1.read(buff1Even);
            }
        };
        final Callable<Integer> read2Even = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream2.read(buff2Even);
            }
        };
        final Callable<Integer> read1Odd = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream1.read(buff1Odd);
            }
        };
        final Callable<Integer> read2Odd = new Callable<Integer>() {
            public Integer call() throws Exception {
                return inputStream2.read(buff2Odd);
            }
        };
        final Callable<Boolean> oddEqualsArray = new Callable<Boolean>() {
            public Boolean call() throws Exception {
                return Arrays.equals(buff1Odd, buff2Odd);
            }
        };
        final Callable<Boolean> evenEqualsArray = new Callable<Boolean>() {
            public Boolean call() throws Exception {
                return Arrays.equals(buff1Even, buff2Even);
            }
        };

        ExecutorService executor = Executors.newCachedThreadPool();
        boolean isEven = true;
        Future<Integer> read1 = null;
        Future<Integer> read2 = null;
        Future<Boolean> isEqual = null;
        int lastSize = 0;
        while (true) {
            if (isEqual != null) {
                if (!isEqual.get()) {
                    return false;
                } else if (lastSize == -1) {
                    return true;
                }
            }
            if (read1 != null) {
                lastSize = read1.get();
                totalbytes += lastSize;
                final int size2 = read2.get();
                if (lastSize != size2) {
                    return false;
                }
            }
            isEven = !isEven;
            if (isEven) {
                if (read1 != null) {
                    isEqual = executor.submit(oddEqualsArray);
                }
                read1 = executor.submit(read1Even);
                read2 = executor.submit(read2Even);
            } else {
                if (read1 != null) {
                    isEqual = executor.submit(evenEqualsArray);
                }
                read1 = executor.submit(read1Odd);
                read2 = executor.submit(read2Odd);
            }
        }
    }

    private boolean isEqualsNio(int myBufferSize) throws IOException {
        FileChannel first = null, seconde = null;
        try {
            first = new FileInputStream(fileName1).getChannel();
            seconde = new FileInputStream(fileName2).getChannel();
            if (first.size() != seconde.size()) {
                return false;
            }
            ByteBuffer firstBuffer = ByteBuffer.allocateDirect(myBufferSize);
            ByteBuffer secondBuffer = ByteBuffer.allocateDirect(myBufferSize);
            int firstRead, secondRead;
            while (first.position() < first.size()) {
                firstRead = first.read(firstBuffer);
                totalbytes += firstRead;
                secondRead = seconde.read(secondBuffer);
                if (firstRead != secondRead) {
                    return false;
                }
                if (!nioBuffersEqual(firstBuffer, secondBuffer, firstRead)) {
                    return false;
                }
            }
            return true;
        } finally {
            if (first != null) {
                first.close();
            }
            if (seconde != null) {
                seconde.close();
            }
        }
    }

    private static boolean nioBuffersEqual(ByteBuffer first, ByteBuffer second, final int length) {
        if (first.limit() != second.limit() || length > first.limit()) {
            return false;
        }
        first.rewind();
        second.rewind();
        for (int i = 0; i < length; i++) {
            if (first.get() != second.get()) {
                return false;
            }
        }
        return true;
    }

    private void printAfterEquals(int myBufferSize) {
        NumberFormat nf = new DecimalFormat("#.00");
        final long dur = System.currentTimeMillis() - start;
        double seconds = dur / 1000d;
        double megabytes = totalbytes / 1024 / 1024;
        double rate = (megabytes) / seconds;
        System.out.println("I was equal, even after " + totalbytes
                + " bytes and reading for " + dur
                + " ms (" + nf.format(rate) + "MB/sec * 2)" +
                " with a buffer size of " + myBufferSize / 1024 + " kB");
    }
}

Autres conseils

Avec ces gros fichiers, vous allez obtenir beaucoup de meilleures performances avec java.nio.

En outre, la lecture des octets individuels avec des flux java peut être très lent. L'utilisation d'un tableau d'octets (2-6k éléments de mes propres expériences, YMMV comme il semble plate-forme / application spécifique) permettra d'améliorer considérablement les performances de lecture avec des flux.

Lecture et écriture des fichiers avec Java peut être tout aussi rapide. Vous pouvez utiliser FileChannels. En ce qui concerne la comparaison des fichiers, évidemment, cela prendra beaucoup de temps à comparer octet à octet Voici un exemple en utilisant FileChannels et ByteBuffers (pourrait être encore optimisé):

public static boolean compare(String firstPath, String secondPath, final int BUFFER_SIZE) throws IOException {
    FileChannel firstIn = null, secondIn = null;
    try {
        firstIn = new FileInputStream(firstPath).getChannel();
        secondIn = new FileInputStream(secondPath).getChannel();
        if (firstIn.size() != secondIn.size())
            return false;
        ByteBuffer firstBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
        ByteBuffer secondBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
        int firstRead, secondRead;
        while (firstIn.position() < firstIn.size()) {
            firstRead = firstIn.read(firstBuffer);
            secondRead = secondIn.read(secondBuffer);
            if (firstRead != secondRead)
                return false;
            if (!buffersEqual(firstBuffer, secondBuffer, firstRead))
                return false;
        }
        return true;
    } finally {
        if (firstIn != null) firstIn.close();
        if (secondIn != null) firstIn.close();
    }
}

private static boolean buffersEqual(ByteBuffer first, ByteBuffer second, final int length) {
    if (first.limit() != second.limit())
        return false;
    if (length > first.limit())
        return false;
    first.rewind(); second.rewind();
    for (int i=0; i<length; i++)
        if (first.get() != second.get())
            return false;
    return true;
}

Ce qui suit est un bon article sur les mérites relatifs des différentes façons de lire un fichier en java. Peut-être d'une certaine utilité:

Comment lire les fichiers rapidement

Après avoir modifié votre NIO fonction de comparaison j'obtenir les résultats suivants.

I was equal, even after 4294967296 bytes and reading for 304594 ms (13.45MB/sec * 2) with a buffer size of 1024 kB
I was equal, even after 4294967296 bytes and reading for 225078 ms (18.20MB/sec * 2) with a buffer size of 4096 kB
I was equal, even after 4294967296 bytes and reading for 221351 ms (18.50MB/sec * 2) with a buffer size of 16384 kB

Remarque: cela signifie que les fichiers sont lus à une vitesse de 37 Mo / s

L'exécution de la même chose sur un lecteur plus rapide

I was equal, even after 4294967296 bytes and reading for 178087 ms (23.00MB/sec * 2) with a buffer size of 1024 kB
I was equal, even after 4294967296 bytes and reading for 119084 ms (34.40MB/sec * 2) with a buffer size of 4096 kB
I was equal, even after 4294967296 bytes and reading for 109549 ms (37.39MB/sec * 2) with a buffer size of 16384 kB

Remarque: cela signifie que les fichiers sont lus à une vitesse de 74,8 Mo / s

private static boolean nioBuffersEqual(ByteBuffer first, ByteBuffer second, final int length) {
    if (first.limit() != second.limit() || length > first.limit()) {
        return false;
    }
    first.rewind();
    second.rewind();
    int i;
    for (i = 0; i < length-7; i+=8) {
        if (first.getLong() != second.getLong()) {
            return false;
        }
    }
    for (; i < length; i++) {
        if (first.get() != second.get()) {
            return false;
        }
    }
    return true;
}

Je trouve que beaucoup des articles liés à ce poste sont vraiment daté (il y a aussi des choses très perspicace aussi). Il y a quelques articles liés à partir de 2001, et l'information est douteuse au mieux. Martin Thompson de sympathie mécanique a écrit un peu à ce sujet en 2011. S'il vous plaît se référer à ce qu'il a écrit pour le contexte et la théorie de cela.

J'ai trouvé que NIO ou non NIO a très peu à voir avec la performance. Il est beaucoup plus sur la taille de vos tampons de sortie (lire tableau d'octets sur celui-là). NIO est pas de magie faire aller la sauce à l'échelle web rapide.

Je suis en mesure de prendre les exemples de Martin et d'utiliser l'OutputStream ère 1.0 et la faire hurler. NIO est rapide aussi, mais le plus grand indicateur est juste la taille de la mémoire tampon de sortie pas si vous utilisez ou non NIO à moins bien sûr que vous utilisez une mémoire mappée NIO il importe. :)

Si vous voulez à jour des informations faisant autorité sur ce sujet, consultez le blog de Martin:

http: //mechanical-sympathy.blogspot. com / 2011/12 / java-séquentiel io-performance.html

Si vous voulez voir comment NIO ne fait pas beaucoup de différence (comme je l'ai pu écrire en utilisant des exemples IO régulière qui étaient plus rapides) voir ceci:

http://www.dzone.com/links/fast_java_io_nio_is_always_faster_than_fileoutput.html

Je l'ai testé mon hypothèse sur les nouveaux ordinateur portable Windows avec un disque dur rapide, mon macbook pro avec SSD, un xlarge EC2, et un EC2 4x grand avec maximisé IOPS / haute vitesse E / S (et bientôt sur un grand disque matrice de disques de fibre NAS) de sorte qu'il fonctionne (il y a quelques problèmes avec elle pour les petites instances EC2 mais si vous vous souciez de la performance ... allez-vous utiliser une petite instance EC2?). Si vous utilisez du matériel réel, dans mes tests jusqu'à présent, IO traditionnelle gagne toujours. Si vous utilisez haut / IO EC2, alors c'est aussi un gagnant. Si vous utilisez dans les instances EC2 puissance, NIO peut gagner.

Il n'y a pas de substitution pour l'analyse comparative.

Quoi qu'il en soit, je ne suis pas expert, je viens de faire quelques tests empiriques utilisant le cadre Sir Martin Thompson a écrit dans son blog.

Je pris cela à l'étape suivante et utilisé Files.newInputStream (à partir de 7 JDK) avec TransferQueue pour créer une recette pour faire Java crier E / S (même sur de petites instances EC2). La recette se trouve au bas de cette documentation pour Boon ( https://github.com/RichardHightower/boon/wiki/Auto-Growable-Byte-Buffer-like-a-ByteBuilder ). Cela me permet d'utiliser un OutputStream traditionnel, mais avec quelque chose qui fonctionne bien sur les instances EC2 plus petites. (Je suis l'auteur principal de Boon. Mais j'accepte de nouveaux auteurs. Le salaire est nul. 0 $ par heure. Mais les bonnes nouvelles sont, je peux doubler votre salaire chaque fois que vous le souhaitez.)

Mes 2 cents.

voir pour voir pourquoi TransferQueue est important. http://php.sabscape.com/blog/?p=557

Les enseignements clés:

  1. Si vous vous souciez des performances jamais, jamais, jamais utiliser BufferedOutputStream .
  2. NIO ne sont pas toujours des performances égales.
  3. taille du tampon est le plus important.
  4. tampons de recyclage pour haut débit est écrit critique.
  5. GC peut / va / n'imploser votre performance pour haut débit écrit.
  6. Vous devez disposer d'un mécanisme de réutiliser les tampons usagés.

DMA / SATA sont techlonogies matériel / bas niveau et ne sont pas visibles à un langage de programmation que ce soit.

Pour vous devez utiliser java.nio, je crois entrée mappée mémoire / sortie.

Êtes-vous sûr que vous ne lisez pas ces fichiers par un octet? Ce serait un gaspillage, je recommande de le faire bloc par bloc, et chaque bloc doit être quelque chose comme 64 méga-octets pour minimiser la recherche.

Essayez de régler le tampon sur le flux d'entrée jusqu'à plusieurs méga-octets.

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