문제

하드 디스크에 두 개의 (각각 2GB) 파일이 있고 서로 비교하고 싶습니다.

  • Windows 탐색기로 원본 파일을 복사하면 약 2-4 분 (읽기와 쓰기 - 동일한 물리적, 논리적 디스크).
  • 함께 읽습니다 java.io.FileInputStream 바이트 당 바이트로 바이트 어레이를 두 번 비교하는 데 20 분 이상이 걸립니다.
  • java.io.BufferedInputStream 버퍼는 64KB이고 파일은 청크로 읽은 다음 비교됩니다.
  • 비교가 완료된 것은 단단한 루프입니다

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

이것을 속도를 높이려면 어떻게해야합니까? Nio는 평범한 개울보다 빠르야합니까? Java는 DMA/SATA 기술을 사용할 수 없으며 일부 느린 OS-API가 대신 전화를 걸나요?

편집하다:
답변 주셔서 감사합니다. 나는 그들을 기반으로 실험을했습니다. 안드레아스가 보여준 것처럼

스트림 또는 nio 접근 방식은 크게 다르지 않습니다.
더 중요한 것은 올바른 버퍼 크기입니다.

이것은 내 자신의 실험에 의해 확인됩니다. 파일이 큰 청크로 읽히면 추가 버퍼 (BufferedInputStream) 아무것도주지 마십시오. 비교 최적화가 가능하고 32 배 Unrolling으로 최상의 결과를 얻었지만 비교 시간은 디스크 읽기에 비해 적은 시간이 적으므로 속도가 작습니다. 내가 할 수있는 일이없는 것 같습니다 ;-(

도움이 되었습니까?

해결책

8kb에서 1MB 사이의 버퍼 크기와 동일한 3,8GB 파일을 비교하는 세 가지 방법을 시도했습니다. 첫 번째 방법은 두 개의 버퍼 입력 스트림을 사용했습니다.

두 번째 접근법은 두 개의 다른 스레드로 읽히고 세 번째 실로 비교하는 ThreadPool을 사용합니다. 이것은 높은 CPU 이용률을 희생시키면서 약간 더 높은 처리량을 얻었습니다. ThreadPool을 관리하는 데는 짧은 실행 작업으로 많은 오버 헤드가 필요합니다.

세 번째 접근 방식은 Laginimaineb에 의해 게시 된대로 Nio를 사용합니다.

보시다시피, 일반적인 접근법은 크게 다르지 않습니다. 더 중요한 것은 올바른 버퍼 크기입니다.

스레드를 사용하여 1 바이트를 덜 읽는 것이 이상합니다. 나는 오류를 힘들게 발견 할 수 없었다.

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

사용 된 코드 :

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

다른 팁

큰 파일로 당신은 훨씬 더 나은 성능을 얻을 것입니다 Java.nio.

또한 Java 스트림으로 단일 바이트를 읽는 것은 매우 느릴 수 있습니다. 바이트 배열 (내 경험의 2-6K 요소, 플랫폼/애플리케이션별로 보이는 YMMV)을 사용하면 스트림으로 읽기 성능이 크게 향상됩니다.

Java로 파일을 읽고 쓰는 것이 빠를 수 있습니다. 당신이 사용할 수있는 파일 앤 넬. 파일을 비교할 때 분명히 바이트를 바이트와 비교하는 데 많은 시간이 걸립니다. 여기에 파일 앤 넬과 바이트 버퍼를 사용하는 예입니다 (추가 최적화 될 수 있음).

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

다음은 Java에서 파일을 읽는 다양한 방법의 상대적 장점에 대한 좋은 기사입니다. 약간의 사용 일 수 있습니다.

파일을 빨리 읽는 방법

NIO 비교 기능을 수정 한 후 다음 결과를 얻습니다.

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

참고 : 이는 파일이 37MB/s의 속도로 읽고 있음을 의미합니다.

더 빠른 드라이브에서 같은 것을 실행합니다

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

참고 : 이것은 파일이 74.8MB/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;
}

당신은 볼 수 있습니다 I/O 튜닝을위한 Suns 기사 (Altough는 이미 약간 날짜가 있습니다), 아마도 예제와 코드 사이의 유사점을 찾을 수 있습니다. 또한 살펴보십시오 Java.nio Java.io보다 빠른 I/O 요소를 포함하는 패키지. Dr. Dobbs Journal은 아주 좋은 기사를 가지고 있습니다 java.nio를 사용하는 고성능 IO.

그렇다면 코드 속도를 높이는 데 도움이 될 수있는 추가 예제 및 튜닝 팁이 있습니다.

또한 배열 클래스가 있습니다 바이트 어레이를 비교하는 방법 건축, 아마도 이것들을 사용하여 물건을 더 빠르게 만들고 루프를 약간 정리할 수 있습니다.

더 나은 비교를 위해 두 파일을 한 번에 복사하십시오. 하드 드라이브는 하나의 파일을 읽는 것보다 훨씬 더 효율적으로 읽을 수 있습니다 (헤드가 읽기 위해 앞뒤로 움직여야하기 때문에)는 더 큰 버퍼 (예 : 16MB)를 사용하는 것입니다. 바이트 버퍼와 함께.

바이트 버퍼를 사용하면 긴 값을 getLong ()와 비교하여 한 번에 8 바이트를 비교할 수 있습니다.

Java가 효율적이면 대부분의 작업은 읽기 및 쓰기를 위해 디스크/OS에 있으므로 다른 언어를 사용하는 것보다 훨씬 느리지 않아야합니다 (디스크/OS가 병목 현상이므로).

코드의 버그가 아니라고 판단 할 때까지 Java가 느리다고 가정하지 마십시오.

나는이 게시물에 링크 된 많은 기사가 실제로 날짜가 있다는 것을 알았습니다 (매우 통찰력있는 것들도 있습니다). 2001 년부터 링크 된 몇 가지 기사가 있으며 정보는 가장 의심 스럽습니다. 기계적 동정의 마틴 톰슨 (Martin Thompson)은 2011 년에 이것에 대해 꽤 많이 썼습니다. 배경과 이론에 대해 그가 쓴 내용을 참조하십시오.

NIO가 아닌 NIO가 성능과 거의 관련이 없다는 것을 알았습니다. 출력 버퍼의 크기에 관한 것입니다 (해당 배열 읽기). Nio는 마법이 아니며 웹 스케일 소스를 빠르게 이동합니다.

나는 Martin의 예를 들고 1.0 ERA 출력 스트림을 사용하여 비명을 지르 셨습니다. NIO도 빠르지 만 가장 큰 지표는 물론 메모리 매핑 NIO를 사용하지 않는 한 NIO를 사용하는지 여부가 아닌 출력 버퍼의 크기 일뿐입니다. :)

이에 대한 최신 권위 정보를 원하시면 Martin의 블로그를 참조하십시오.

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

NIO가 어떻게 그렇게 많은 차이를 만들지 않는지 알고 싶다면 (더 빠른 일반 IO를 사용하여 예제를 작성할 수 있었기 때문에) 다음을 참조하십시오.

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

빠른 하드 디스크, SSD가 포함 된 MacBook Pro, EC2 XLARGE 및 최대 IOPS/고속 I/O가있는 EC2 4 배의 새로운 Windows 노트북에서 내 가정을 테스트했습니다 (그리고 곧 대형 디스크 NAS 섬유 디스크 Array)는 작동합니다 (더 작은 EC2 인스턴스에는 몇 가지 문제가 있지만 성능에 관심이 있다면 ... 작은 EC2 인스턴스를 사용 하시겠습니까?). 실제 하드웨어를 사용하는 경우 지금까지 내 테스트에서 전통적인 IO는 항상 승리합니다. High/IO EC2를 사용하는 경우 이것은 분명한 승자이기도합니다. 전원 EC2 인스턴스에서 사용하면 NIO가 이길 수 있습니다.

벤치마킹을위한 대체물이 없습니다.

어쨌든, 나는 전문가가 아닙니다. 저는 Martin Thompson 경이 그의 블로그 게시물에 쓴 프레임 워크를 사용하여 경험적 테스트를했습니다.

나는 이것을 다음 단계로 가져 가서 사용했습니다 files.newinputStream (JDK 7)와 함께 트랜스퍼 큐 Java I/O가 비명을 지르기위한 레시피를 만듭니다 (작은 EC2 인스턴스에서도). 레시피는 BOON에 대한이 문서의 맨 아래에서 찾을 수 있습니다 (https://github.com/richardhightower/boon/wiki/auto-growable-byte-buffer-like-bytebuilder). 이를 통해 전통적인 출력 스트림을 사용할 수 있지만 더 작은 EC2 인스턴스에서 잘 작동하는 기능이 있습니다. (나는 Boon의 주요 저자입니다. 그러나 나는 새로운 저자를 받아들이고 있습니다. 급여는 짜증나.

내 2 센트.

이유를 보려면 이것을 참조하십시오 트랜스퍼 큐 중요하다. http://php.sabscape.com/blog/?p=557

주요 학습 :

  1. 성능에 관심이 있다면 절대 사용하지 마십시오. BufferedOutputStream.
  2. NIO는 항상 성능과 동일하지는 않습니다.
  3. 버퍼 크기가 가장 중요합니다.
  4. 고속 쓰기를위한 재활용 버퍼가 중요합니다.
  5. GC는 고속 쓰기에 대한 성과를 낼 수 있습니다.
  6. 소비 된 버퍼를 재사용 할 수있는 메커니즘이 있어야합니다.

DMA/SATA는 하드웨어/저수준 TechLonogies이며 어떤 프로그래밍 언어에도 보이지 않습니다.

메모리 매핑 입력/출력의 경우 java.nio를 사용해야합니다.

하나의 바이트로 해당 파일을 읽지 않습니까? 그것은 낭비 될 것입니다. 나는 블록 별 블록을하는 것이 좋습니다. 각 블록은 찾기를 최소화하기 위해 64 메가 바이트와 같은 것이어야합니다.

입력 스트림에서 버퍼를 여러 메가 바이트까지 설정해보십시오.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top