0

私は、バイナリファイル(最大50メガ)から膨大な数の整数を処理するアプリケーションを作成中です。私はできるだけ早くそれを行う必要があり、主なパフォーマンスの問題はディスクアクセス時間です。ディスクから多数の読み取りを行うため、読み取り時間を最適化すると、一般的にアプリのパフォーマンスが向上します。

これまで、ファイルを分割するブロックが少ないほど(つまり、読み取りが少ない/読み取りサイズが大きい)、アプリの動作が速くなると考えていました。これは、HDDがその機械的性質のために、ブロックの先頭を見つけるのが非常に遅いためです。ただし、読み取りを要求したブロックの先頭が見つかると、実際の読み取りをかなり迅速に実行する必要があります。

まあ、それは私がこのテストを実行するまででした:

古いテストが削除され、HDDキャッシングが原因で問題が発生しました

新しいテスト(ファイルが大きすぎて(1GB)、ファイル内のランダムな場所にアクセスするため、HDDキャッシュはここでは役に立ちません):

    int mega = 1024 * 1024;
    int giga = 1024 * 1024 * 1024;
    byte[] bigBlock = new byte[mega];
    int hundredKilo = mega / 10;
    byte[][] smallBlocks = new byte[10][hundredKilo];
    String location = "C:\\Users\\Vladimir\\Downloads\\boom.avi";
    RandomAccessFile raf;
    FileInputStream f;
    long start;
    long end;
    int position;
    java.util.Random rand = new java.util.Random();
    int bigBufferTotalReadTime = 0;
    int smallBufferTotalReadTime = 0;

    for (int j = 0; j < 100; j++)
    {
        position = rand.nextInt(giga);
        raf = new RandomAccessFile(location, "r");
        raf.seek((long) position);
        f = new FileInputStream(raf.getFD());
        start = System.currentTimeMillis();
        f.read(bigBlock);
        end = System.currentTimeMillis();
        bigBufferTotalReadTime += end - start;
        f.close();
    }

    for (int j = 0; j < 100; j++)
    {
        position = rand.nextInt(giga);
        raf = new RandomAccessFile(location, "r");
        raf.seek((long) position);
        f = new FileInputStream(raf.getFD());
        start = System.currentTimeMillis();
        for (int i = 0; i < 10; i++)
        {
            f.read(smallBlocks[i]);
        }
        end = System.currentTimeMillis();
        smallBufferTotalReadTime += end - start;
        f.close();
    }

    System.out.println("Average performance of small buffer: " + (smallBufferTotalReadTime / 100));
    System.out.println("Average performance of big buffer: " + (bigBufferTotalReadTime / 100));

結果:小さなバッファーの平均-35ms大きなバッファーの平均-40ms?!(LinuxとWindowsで試してみましたが、どちらの場合もブロックサイズが大きいほど読み取り時間が長くなります。なぜですか?)

このテストを何度も実行した後、魔法の理由で、1つの大きなブロックを読み取ると、小さいサイズの10ブロックを順番に読み取るよりも平均して時間がかかることに気付きました。Windowsが賢すぎて、ファイルシステムで何かを最適化しようとした結果かもしれないと思ったので、Linuxで同じコードを実行しましたが、驚いたことに同じ結果が得られました。

なぜこれが起こっているのか私にはわかりません、誰かが私にヒントを教えてもらえますか?また、この場合の最適なブロックサイズは何ですか?

敬具

4

2 に答える 2

1

初めてデータを読み取った後、データはディスクキャッシュに保存されます。2回目の読み取りははるかに高速です。最初に、より速いと思うテストを実行する必要があります。;)

50 MBのメモリがある場合は、ファイル全体を一度に読み取ることができるはずです。


package com.google.code.java.core.files;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileReadingMain {
    public static void main(String... args) throws IOException {
        File temp = File.createTempFile("deleteme", "zeros");
        FileOutputStream fos = new FileOutputStream(temp);
        fos.write(new byte[50 * 1024 * 1024]);
        fos.close();

        for (int i = 0; i < 3; i++)
            for (int blockSize = 1024 * 1024; blockSize >= 512; blockSize /= 2) {
                readFileNIO(temp, blockSize);
                readFile(temp, blockSize);
            }
    }

    private static void readFile(File temp, int blockSize) throws IOException {
        long start = System.nanoTime();
        byte[] bytes = new byte[blockSize];
        int r;
        for (r = 0; System.nanoTime() - start < 2e9; r++) {
            FileInputStream fis = new FileInputStream(temp);
            while (fis.read(bytes) > 0) ;
            fis.close();
        }
        long time = System.nanoTime() - start;
        System.out.printf("IO: Reading took %.3f ms using %,d byte blocks%n", time / r / 1e6, blockSize);
    }

    private static void readFileNIO(File temp, int blockSize) throws IOException {
        long start = System.nanoTime();
        ByteBuffer bytes = ByteBuffer.allocateDirect(blockSize);
        int r;
        for (r = 0; System.nanoTime() - start < 2e9; r++) {
            FileChannel fc = new FileInputStream(temp).getChannel();
            while (fc.read(bytes) > 0) {
                bytes.clear();
            }
            fc.close();
        }
        long time = System.nanoTime() - start;
        System.out.printf("NIO: Reading took %.3f ms using %,d byte blocks%n", time / r / 1e6, blockSize);
    }
}

私のラップトッププリントで

NIO: Reading took 57.255 ms using 1,048,576 byte blocks
IO: Reading took 112.943 ms using 1,048,576 byte blocks
NIO: Reading took 48.860 ms using 524,288 byte blocks
IO: Reading took 78.002 ms using 524,288 byte blocks
NIO: Reading took 41.474 ms using 262,144 byte blocks
IO: Reading took 61.744 ms using 262,144 byte blocks
NIO: Reading took 41.336 ms using 131,072 byte blocks
IO: Reading took 56.264 ms using 131,072 byte blocks
NIO: Reading took 42.184 ms using 65,536 byte blocks
IO: Reading took 64.700 ms using 65,536 byte blocks
NIO: Reading took 41.595 ms using 32,768 byte blocks <= fastest for NIO
IO: Reading took 49.385 ms using 32,768 byte blocks <= fastest for IO
NIO: Reading took 49.676 ms using 16,384 byte blocks
IO: Reading took 59.731 ms using 16,384 byte blocks
NIO: Reading took 55.596 ms using 8,192 byte blocks
IO: Reading took 74.191 ms using 8,192 byte blocks
NIO: Reading took 77.148 ms using 4,096 byte blocks
IO: Reading took 84.943 ms using 4,096 byte blocks
NIO: Reading took 104.242 ms using 2,048 byte blocks
IO: Reading took 112.768 ms using 2,048 byte blocks
NIO: Reading took 177.214 ms using 1,024 byte blocks
IO: Reading took 185.006 ms using 1,024 byte blocks
NIO: Reading took 303.164 ms using 512 byte blocks
IO: Reading took 316.487 ms using 512 byte blocks

最適な読み取りサイズは32KBのようです。注:ファイルは完全にディスクキャッシュにあるため、これはディスクから読み取られるファイルに最適なサイズではない可能性があります。

于 2011-07-16T00:01:24.180 に答える
1

前述のように、それぞれについて同じデータを読み取ることで、テストが危険にさらされる可能性があります。

私は吐き出すことができますが、おそらくこの記事を読んでから、FileChannelの使用方法の この例を見るとさらに多くのことがわかるでしょう。

于 2011-07-16T01:00:13.630 に答える