【问题标题】:Unexpected behaviour with FileInputStream, JAVAFileInputStream、JAVA 的意外行为
【发布时间】:2011-10-06 13:23:45
【问题描述】:

我正在编写一个应用程序来处理来自二进制文件(最多 50 兆)的大量整数。我需要尽快完成,主要的性能问题是磁盘访问时间,因为我从磁盘进行大量读取,优化读取时间通常会提高应用程序的性能。

到目前为止,我认为我将文件拆分成的块越少(即读取次数越少/读取大小越大),我的应用程序运行速度就越快。这是因为 HDD 由于其机械特性而在寻找(即定位块的开头)时非常慢。但是,一旦它找到了您要求它读取的块的开头,它应该会相当快地执行实际读取。

嗯,直到我运行这个测试:

旧测试已删除,由于 HDD 缓存而出现问题

新测试(硬盘缓存在这里没有帮助,因为文件太大(1gb)并且我访问其中的随机位置):

    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 大缓冲区的平均值 - 40 毫秒?! (在linux和windows上试过,在这两种情况下,更大的块大小会导致更长的读取时间,为什么?)

在多次运行此测试后,我意识到由于某种神奇的原因,读取一个大块平均比依次读取 10 个较小的块花费更长的时间。我认为这可能是由于 Windows 过于智能并试图优化其文件系统中的某些内容,所以我在 Linux 上运行了相同的代码,令我惊讶的是,我得到了相同的结果。

我不知道为什么会发生这种情况,有人可以给我提示吗?在这种情况下,最好的块大小是多少?

亲切的问候

【问题讨论】:

    标签: java performance filesystems operating-system fileinputstream


    【解决方案1】:

    第一次读取数据后,数据将在磁盘缓存中。第二次读取应该快得多。您需要先运行您认为更快的测试。 ;)

    如果您有 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。注意:由于文件完全在磁盘缓存中,这可能不是从磁盘读取文件的最佳大小。

    【讨论】:

    • PS 我没有 50 MB 的内存 :))
    • 但是你有硬盘??这是带存储卡的移动设备吗?
    • “你需要先运行你认为更快的测试”
    • 不,这是一种算法,它将被限制在一定数量的 RAM 中,谁提出最快的,谁赢:)
    • 如果您有移动设备,您需要测试您的设备的最佳块大小。我会从 8K 开始,即使在 PC 或服务器上也是合理的。如果您有存储卡,则块的大小不太重要。
    【解决方案2】:

    如前所述,您的测试因读取每个数据的相同数据而受到无可救药的影响。

    我可以继续说下去,但您可能会从阅读this article, 然后查看如何使用 FileChannel 的 this example 获得更多信息。

    【讨论】:

    • 好文章,谢谢,明早起来看看! :)
    • 当然,您的里程有所不同。此外,关于 FileChannel 是否真的更快,如果是,在什么条件下使用什么技术,存在很多争论。您可以在网络上找到几乎支持任何观点的东西,除了在开始之前完成的阅读。
    • 好的,原来我们被禁止使用 FileChannel :(
    • “禁止”是谁?档案警察?
    • 哈哈哈,不:)。这是一个编写算法的竞赛,我们不允许使用 FileChannel 来获得我们的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2021-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多