17

ハードディスクに 2 つのファイル (それぞれ 2GB) があり、それらを相互に比較したいと考えています。

  • Windows エクスプローラーで元のファイルをコピーするには、約 10 分かかります。2 ~ 4 分 (つまり、同じ物理ディスクと論理ディスクでの読み取りと書き込み)。
  • 2 回読み取り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 倍の展開で最良の結果が得られましたが、比較にかかる時間はディスク読み取りに比べて小さいため、高速化はわずかです。どうしようもないですね(-_-;)

4

10 に答える 10

16

バッファサイズが8kb〜1MBの2つの同一の3,8gbファイルを比較する3つの異なる方法を試しました。最初の最初の方法では、2つのバッファリングされた入力ストリームのみを使用しました

2番目のアプローチでは、2つの異なるスレッドを読み取り、3番目のスレッドで比較するスレッドプールを使用します。これにより、CPU使用率が高くなる代わりに、スループットがわずかに高くなりました。スレッドプールの管理には、これらの短期間のタスクで多くのオーバーヘッドがかかります。

laginimainebによって投稿されたように、3番目のアプローチは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");
    }
}
于 2009-06-11T10:20:30.737 に答える
8

このような大きなファイルでは、 java.nioを使用するとパフォーマンスが大幅に向上します。

さらに、Java ストリームでの単一バイトの読み取りは非常に遅くなる可能性があります。バイト配列 (私自身の経験から 2 ~ 6K の要素、プラットフォーム/アプリケーション固有のように見える ymmv) を使用すると、ストリームでの読み取りパフォーマンスが劇的に向上します。

于 2009-06-08T11:00:29.757 に答える
7

Java を使用したファイルの読み取りと書き込みは、同じくらい高速です。FileChannelsを使用できます。ファイルの比較に関しては、バイト単位で比較すると明らかに時間がかかります。ここでは、FileChannels と ByteBuffers を使用した例を示します (さらに最適化することができます)。

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;
}
于 2009-06-08T11:01:48.040 に答える
6

以下は、Java でファイルを読み取るさまざまな方法の相対的な利点に関する優れた記事です。役に立つかもしれません:

ファイルをすばやく読み取る方法

于 2009-06-08T11:08:02.287 に答える
6

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

注: これは、ファイルが 37 MB/秒の速度で読み取られていることを意味します

より高速なドライブで同じことを実行する

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.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;
}
于 2009-06-12T06:58:44.847 に答える
3

この投稿にリンクされている記事の多くは、実際には古くなっていることがわかりました (非常に洞察に満ちたものもあります)。2001 年からリンクされている記事がいくつかありますが、その情報はせいぜい疑わしいものです。Mechanical sympathy の Martin Thompson は、2011 年にこれについてかなりの量の記事を書いています。この背景と理論については、彼の記事を参照してください。

NIO であろうとなかろうと、パフォーマンスとはほとんど関係がないことがわかりました。それは、出力バッファーのサイズに関するものです (そのバッファーのバイト配列を読み取ります)。NIO は魔法ではありません。Web スケール ソ​​ースを高速化します。

Martin の例を参考にして、1.0 時代の OutputStream を使用し、悲鳴を上げることができました。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

私は、高速ハードディスクを備えた新しい Windows ラップトップ、SSD を備えた私の MacBook Pro、EC2 xlarge、および IOPS/高速 I/O を最大化した EC2 4x ラージ (そしてすぐに大容量ディスク NAS ファイバー ディスク) で私の仮定をテストしました。配列) であるため、動作します (小さな EC2 インスタンスではいくつか問題がありますが、パフォーマンスを気にする場合は... 小さな EC2 インスタンスを使用しますか?)。実際のハードウェアを使用する場合、これまでの私のテストでは、従来の IO が常に勝ちます。高 IO EC2 を使用している場合、これも明らかに勝者です。低電力の EC2 インスタンスを使用する場合、NIO が勝つ可能性があります。

ベンチマークに代わるものはありません。

とにかく、私は専門家ではありません。Martin Thompson 卿がブログ投稿で書いたフレームワークを使用して、経験的なテストを行っただけです。

これを次のステップに進め、 Files.newInputStream (JDK 7 から) をTransferQueueと共に使用して、Java I/O を絶叫させるためのレシピを作成しました (小さな EC2 インスタンスでも)。レシピは、Boon のこのドキュメントの下部にあります ( https://github.com/RichardHightower/boon/wiki/Auto-Growable-Byte-Buffer-like-a-ByteBuilder )。これにより、従来の OutputStream を使用できますが、小さな EC2 インスタンスでうまく機能するものを使用できます。(私は Boon の主な著者です。しかし、私は新しい著者を受け入れています。給料は最低です。時給は 0 ドルです。しかし良いニュースは、いつでもあなたの給料を 2 倍にすることができるということです。)

私の2セント。

TransferQueueが重要な理由については、こちらを参照してください。http://php.sabscape.com/blog/?p=557

主な学習内容:

  1. パフォーマンスが気になる場合は、決してBufferedOutputStreamを使用しないでください。
  2. NIO は必ずしもパフォーマンスに匹敵するわけではありません。
  3. バッファサイズが最も重要です。
  4. 高速書き込みのためのバッファのリサイクルは重要です。
  5. GC は、高速書き込みのパフォーマンスを低下させる可能性があります。
  6. 使用済みバッファを再利用するメカニズムが必要です。
于 2013-11-09T22:48:39.840 に答える
2

Suns Article for I/O Tuning (既に少し古いですが) を参照してください。おそらく、そこにある例とコードの間に類似点を見つけることができます。java.ioよりも高速な I/O 要素を含むjava.nioパッケージも見てください。Dr. Dobbs Journal には、java.nio を使用した高性能 IOに関する非常に優れた記事があります。

その場合、コードの高速化に役立つその他の例とチューニングのヒントがそこにあります。

さらに、Arrays クラスには、バイト配列を比較するためのメソッドが組み込まれています。これらを使用して、処理を高速化し、ループを少しクリアすることもできます。

于 2009-06-08T11:06:28.467 に答える
1

より良い比較のために、一度に 2 つのファイルをコピーしてみてください。ハード ドライブは、2 つのファイルを読み取るよりも 1 つのファイルをはるかに効率的に読み取ることができます (読み取るためにヘッドが前後に移動する必要があるため)。バイトバッファで。

ByteBuffer を使用すると、getLong() で long 値を比較することにより、一度に 8 バイトを比較できます。

Java が効率的である場合、ほとんどの作業は読み取りと書き込みのためにディスク/OS で行われるため、他の言語を使用する場合よりも遅くなることはありません (ディスク/OS がボトルネックであるため)。

コードのバグではないと判断するまで、Java が遅いと思い込まないでください。

于 2009-06-09T06:32:19.717 に答える
0

DMA/SATA はハードウェア/低レベルのテクノロジであり、どのプログラミング言語からも見えません。

メモリ マップされた入力/出力には、java.nio を使用する必要があると思います。

これらのファイルを 1 バイト単位で読み取っていませんか? ブロックごとに行うことをお勧めします。シークを最小限に抑えるには、各ブロックを 64 メガバイト程度にする必要があります。

于 2009-06-08T11:03:07.130 に答える
-1

入力ストリームのバッファーを数メガバイトまで設定してみてください。

于 2009-06-13T13:47:10.380 に答える