为什么FileInputStream读取的数据越大越好

Sti*_*tig 8 java io performance fileinputstream

如果我将文件中的字节读入byte [],我发现当数组大约1 MB而不是128 KB时,FileInputStream的性能会更差.在我测试的2个工作站上,它几乎是128 KB的两倍.这是为什么?

import java.io.*;

public class ReadFileInChuncks 
{
    public static void main(String[] args) throws IOException 
    {
        byte[] buffer1 = new byte[1024*128];
        byte[] buffer2 = new byte[1024*1024];

        String path = "some 1 gb big file";

        readFileInChuncks(path, buffer1, false);

        readFileInChuncks(path, buffer1, true);
        readFileInChuncks(path, buffer2, true);
        readFileInChuncks(path, buffer1, true);
        readFileInChuncks(path, buffer2, true);
    }

    public static void readFileInChuncks(String path, byte[] buffer, boolean report) throws IOException
    {
        long t = System.currentTimeMillis();

        InputStream is = new FileInputStream(path);
        while ((readToArray(is, buffer)) != 0) {}

        if (report)
            System.out.println((System.currentTimeMillis()-t) + " ms");
    }

    public static int readToArray(InputStream is, byte[] buffer) throws IOException
    {
        int index = 0;
        while (index != buffer.length)
        {
            int read = is.read(buffer, index, buffer.length - index);
            if (read == -1)
                break;
            index += read;
        }
        return index;
    }
}
Run Code Online (Sandbox Code Playgroud)

输出

422 ms 
717 ms 
422 ms 
718 ms
Run Code Online (Sandbox Code Playgroud)

请注意,这是对已发布问题的重新定义.另一方受到无关讨论的污染.我会将另一个标记为删除.

编辑:重复,真的吗?我当然可以做一些更好的代码,以证明我的观点,但是并没有回答我的问题

Edit2:我在
Win7/JRE 1.8.0_25 上运行了5 KB到1000 KB之间的每个缓冲区的测试,并且错误的性能从精确的508 KB和随后的所有内容开始.对不起的图表军团很抱歉,x是缓冲区大小,y是毫秒

在此输入图像描述

apa*_*gin 8

TL; DR性能下降是由内存分配引起的,而不是由文件读取问题引起的.

典型的基准测试问题:您对一件事进行基准测试,但实际上衡量另一件事

首先,当我使用重写了代码示例RandomAccessFile,FileChannel并且ByteBuffer.allocateDirect,门槛已经消失了.对于128K和1M缓冲区,文件读取性能大致相同.

与直接ByteBuffer不同,I/O FileInputStream.read无法将数据直接加载到Java字节数组中.它需要首先将数据放入某个本机缓冲区,然后使用JNI SetByteArrayRegion函数将其复制到Java .

所以我们必须看一下本机的实现FileInputStream.read.它归结为io_util.c中的以下代码:

    if (len == 0) {
        return 0;
    } else if (len > BUF_SIZE) {
        buf = malloc(len);
        if (buf == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            return 0;
        }
    } else {
        buf = stackBuf;
    }
Run Code Online (Sandbox Code Playgroud)

这里BUF_SIZE == 8192.如果缓冲区大于此保留堆栈区域,则分配临时缓冲区malloc.在Windows malloc上通常通过HeapAlloc WINAPI调用实现.

接下来,我在没有文件I/O的情况下单独测量了HeapAlloc+ HeapFree调用的性能.结果很有趣:

     128K:    5 ?s
     256K:   10 ?s
     384K:   15 ?s
     512K:   20 ?s
     640K:   25 ?s
     768K:   29 ?s
     896K:   33 ?s
    1024K:  316 ?s  <-- almost 10x leap
    1152K:  356 ?s
    1280K:  399 ?s
    1408K:  436 ?s
    1536K:  474 ?s
    1664K:  511 ?s
    1792K:  553 ?s
    1920K:  592 ?s
    2048K:  628 ?s
Run Code Online (Sandbox Code Playgroud)

如您所见,OS内存分配的性能在1MB边界处急剧变化.这可以通过用于小块和大块的不同分配算法来解释.

UPDATE

HeapCreate的文档确认了关于大于1MB的块的特定分配策略的想法(请参阅dwMaximumSize描述).

此外,可以从堆分配的最大内存块对于32位进程略小于512 KB,对于64位进程略小于1,024 KB.

...

分配大于固定大小堆限制的内存块的请求不会自动失败; 相反,系统调用VirtualAlloc函数来获取大块所需的内存.