Domanda

Ho due file (2 GB ciascuno) sul mio disco rigido e desidero confrontarli tra loro:

  • La copia dei file originali con Windows Explorer richiede ca.2-4 minuti (ovvero lettura e scrittura - sullo stesso disco fisico e logico).
  • Leggere con java.io.FileInputStream due volte e il confronto degli array di byte su base byte per byte richiede più di 20 minuti.
  • java.io.BufferedInputStream buffer è 64kb, i file vengono letti in blocchi e quindi confrontati.
  • Il confronto è fatto come se fosse un ciclo stretto

    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];
       }
    }
    

Cosa posso fare per accelerare il processo?NIO dovrebbe essere più veloce dei normali flussi?Java non è in grado di utilizzare le tecnologie DMA/SATA e esegue invece alcune chiamate OS-API lente?

MODIFICARE:
Grazie per le risposteHo fatto alcuni esperimenti basati su di essi.Come ha mostrato Andreas

flussi o nio gli approcci non differiscono molto.
Ancora più importante è la dimensione corretta del buffer.

Ciò è confermato dai miei esperimenti.Poiché i file vengono letti in grandi porzioni, anche i buffer aggiuntivi (BufferedInputStream) non dare nulla.È possibile ottimizzare il confronto e ho ottenuto i migliori risultati con lo srotolamento di 32 volte, ma il tempo impiegato nel confronto è ridotto rispetto alla lettura del disco, quindi la velocità è ridotta.Sembra che non ci sia niente da fare ;-(

È stato utile?

Soluzione

Ho provato tre diversi metodi per confrontare due file identici da 3,8 GB con dimensioni del buffer comprese tra 8 kb e 1 MB.il primo metodo utilizzava solo due flussi di input bufferizzati

il secondo approccio utilizza un threadpool che legge due thread diversi e li confronta in un terzo.questo ha ottenuto un throughput leggermente superiore a scapito di un elevato utilizzo della CPU.la gestione del threadpool richiede molto sovraccarico con queste attività di breve durata.

il terzo approccio utilizza nio, come pubblicato da laginimaineb

come puoi vedere, l'approccio generale non differisce molto.più importante è la dimensione corretta del buffer.

cosa c'è di strano che leggo 1 byte in meno usando i thread.non sono riuscito a individuare l'errore.

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

il codice utilizzato:

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

Altri suggerimenti

Con tali file di grandi dimensioni, che si sta per ottenere prestazioni molto migliori con java.nio.

Inoltre, la lettura byte singoli con flussi Java può essere molto lento. Utilizzando un array di byte (2-6K elementi da mie esperienze, YMMV come sembra piattaforma / specifica applicazione) migliorerà notevolmente le prestazioni in lettura con i flussi.

Leggere e scrivere i file con Java può essere altrettanto veloce. È possibile utilizzare FileChannels . Per quanto riguarda il confronto dei file, ovviamente questo ci vorrà un sacco di tempo a confronto byte per byte Ecco un esempio utilizzando FileChannels e ByteBuffers (potrebbe essere ulteriormente ottimizzate):

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;
}

Il seguente è un buon articolo sui meriti relativi dei diversi modi di leggere un file in java. Può essere di qualche utilità:

Come leggere i file in modo rapido

Dopo aver modificato il tuo NIO confrontare funzione ottengo i seguenti risultati.

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

Nota: questo significa che i file vengono letti a una velocità di 37 MB / s

L'esecuzione la stessa cosa su un disco più veloce

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

Nota: questo significa che i file vengono letti a una velocità di 74,8 MB / 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;
}

Si può avere uno sguardo a Soli articolo per I / O di sintonia (benche già un po 'datato), forse si può trovare similitudini tra gli esempi lì e il vostro codice. Inoltre avere uno sguardo alla java. pacchetto nio che contiene elementi O più veloci rispetto a I / java.io. Dr. Dobbs Journal ha un bel bell'articolo su elevate prestazioni IO usando java.nio .

Se è così, ci sono ulteriori esempi e suggerimenti di tuning disponibili là che dovrebbe essere in grado di aiutare ad accelerare il vostro codice.

Inoltre la classe Arrays ha metodi per confrontare array di byte costruire, forse questi possono anche essere usati per fare le cose più velocemente e chiarire il ciclo un po '.

Per una migliore comparazione provare a copiare due file contemporaneamente. Un disco rigido in grado di leggere un file molto più efficiente rispetto alla lettura di due (come la testa deve muoversi avanti e indietro a leggere) Un modo per ridurre questo è usare buffer più grandi, ad esempio 16 MB. con ByteBuffer.

Con ByteBuffer è possibile confrontare 8-byte alla volta confrontando i valori di lunghezza con getLong ()

Se il tuo Java è efficiente, la maggior parte del lavoro è nel disco / OS per la lettura e la scrittura in modo che non dovrebbe essere molto più lento rispetto all'utilizzo di qualsiasi altra lingua (come il disco / OS è il collo di bottiglia)

Non date per scontato Java è lento fino a quando si è determinato il suo non è un bug nel codice.

ho scoperto che un sacco di articoli legati a in questo post sono davvero fuori datato (c'è anche alcune cose molto perspicace troppo). Ci sono alcuni articoli collegati a 2001 e le informazioni è discutibile al meglio. Martin Thompson di simpatia meccanico ha scritto un bel po 'di questo nel 2011. Si prega di fare riferimento a ciò che ha scritto per lo sfondo e la teoria di questo.

Ho trovato che NIO o no NIO ha ben poco a che fare con le prestazioni. E 'molto di più la dimensione dei buffer di uscita (leggi array di byte su quello). NIO c'è magia farlo andare salsa di scala veloce web.

Sono stato in grado di prendere esempi di Martin e utilizzare l'OutputStream 1.0 un'epoca e farlo urlare. NIO è troppo veloce, ma il più grande indicatore è solo la dimensione del buffer di uscita non è se o non si utilizza NIO a meno che naturalmente si sta utilizzando una memoria mappata NIO quindi è importante. :)

Se si vuole aggiornate informazioni autorevoli su questo, vedere il blog di Martin:

http: //mechanical-sympathy.blogspot. com / 2011/12 / java-sequenziale-io-performance.html

Se volete vedere come NIO non fa che molta differenza (come mi è stato in grado di scrivere esempi di utilizzo regolare IO che erano più veloci) vedere questo:

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

Ho testato la mia ipotesi sulle nuove finestre computer portatile con un disco rigido veloce, il mio MacBook Pro con SSD, un xlarge EC2, e un EC2 4x grande con maxed IOPS / alta velocità di I / O (e presto su un disco di grandi dimensioni NAS array di dischi fibre) in modo che funziona (ci sono alcuni problemi con esso per le istanze EC2 più piccoli, ma se vi preoccupate per le prestazioni ... hai intenzione di usare una piccola istanza EC2?). Se si utilizza hardware reale, nelle mie prove finora, tradizionale IO vince sempre. Se si utilizza alta / IO EC2, allora questo è anche un chiaro vincitore. Se si utilizza sotto istanze EC2 alimentati, NIO può vincere.

Non v'è alcuna sostituzione per il benchmarking.

In ogni caso, non sono un esperto, ho solo fatto qualche verifica empirica utilizzando il framework che Sir Martin Thompson ha scritto nel suo blog.

Ho preso questo per il passo successivo e usato Files.newInputStream (da JDK 7) con TransferQueue per creare una ricetta per fare Java I / O urlo (anche su istanze piccole EC2). La ricetta può essere trovato in fondo a questa documentazione per Boon ( https://github.com/RichardHightower/boon/wiki/Auto-Growable-Byte-Buffer-like-a-ByteBuilder ). Questo mi permette di utilizzare un OutputStream tradizionale, ma con qualcosa che funziona bene su istanze EC2 più piccoli. (Io sono l'autore principale della Boon. Ma sto accettando nuovi autori. La paga fa schifo. 0 $ all'ora. Ma la buona notizia è, posso raddoppiare la paga ogni volta che vuoi.)

I miei 2 centesimi.

Vedere questo per capire perché TransferQueue è importante. http://php.sabscape.com/blog/?p=557

apprendimenti chiave:

  1. Se vi preoccupate per prestazioni mai, mai, mai usare BufferedOutputStream .
  2. NIO non sempre pari prestazioni.
  3. dimensione del buffer che conta di più.
  4. buffer di riciclaggio per alta velocità scrive è un fattore critico.
  5. GC può / vi / non implodere le prestazioni ad alta velocità scrive.
  6. Devi avere qualche meccanismo di riutilizzare i buffer spesi.

DMA / SATA sono techlonogies / basso livello hardware e non sono visibili a qualsiasi linguaggio di programmazione di sorta.

Per la memoria mappata input / output si dovrebbe usare java.nio, credo.

Sei sicuro che non state leggendo i file da un byte? Questo sarebbe uno spreco, mi consiglia di farlo blocco per blocco, e ciascun blocco dovrebbe essere qualcosa di simile a 64 megabyte per ridurre al minimo la ricerca.

Provare a impostare il buffer sull'ingresso streaming fino a diversi megabyte.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top