1

我试图从一个可能很大的文件中读取行。

为了获得更好的性能,我尝试使用映射文件。但是当我比较性能时,我发现映射文件方式甚至比我读取的速度慢一点BufferedReader

public long chunkMappedFile(String filePath, int trunkSize) throws IOException {
    long begin = System.currentTimeMillis();
    logger.info("Processing imei file, mapped file [{}], trunk size = {} ", filePath, trunkSize);

    //Create file object
    File file = new File(filePath);

    //Get file channel in readonly mode
    FileChannel fileChannel = new RandomAccessFile(file, "r").getChannel();

    long positionStart = 0;
    StringBuilder line = new StringBuilder();
    long lineCnt = 0;
    while(positionStart < fileChannel.size()) {
        long mapSize = positionStart + trunkSize < fileChannel.size() ? trunkSize : fileChannel.size()  - positionStart ;
        MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, positionStart, mapSize);//mapped read
        for (int i = 0; i < buffer.limit(); i++) {
            char c = (char) buffer.get();
            //System.out.print(c); //Print the content of file
            if ('\n' != c) {
                line.append(c);
            } else {// line ends
                processor.processLine(line.toString());
                if (++lineCnt % 100000 ==0) {
                    try {
                        logger.info("mappedfile processed {} lines already, sleep 1ms", lineCnt);
                        Thread.sleep(1);
                    } catch (InterruptedException e) {}
                }
                line = new StringBuilder();
            }
        }
        closeDirectBuffer(buffer);
        positionStart = positionStart + buffer.limit();
    }

    long end = System.currentTimeMillis();
    logger.info("chunkMappedFile {} , trunkSize: {},  cost : {}  " ,filePath, trunkSize, end - begin);

    return lineCnt;
}

public long normalFileRead(String filePath) throws IOException {
    long begin = System.currentTimeMillis();
    logger.info("Processing imei file, Normal read file [{}] ", filePath);
    long lineCnt = 0;
    try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
        String line;

        while ((line = br.readLine()) != null) {
            processor.processLine(line.toString());
            if (++lineCnt % 100000 ==0) {
                try {
                    logger.info("file processed {} lines already, sleep 1ms", lineCnt);
                    Thread.sleep(1);
                } catch (InterruptedException e) {}
            }            }
    }
    long end = System.currentTimeMillis();
    logger.info("normalFileRead {} ,   cost : {}  " ,filePath, end - begin);

    return lineCnt;
}

在 Linux 中读取大小为 537MB 的文件的测试结果:

MappedBuffer方式:

2017-09-28 14:33:19.277 [main] INFO  com.oppo.push.ts.dispatcher.imei2device.ImeiTransformerOfflineImpl - process imei file ends:/push/file/imei2device-local/20170928/imei2device-13 , lines :12758858 , cost :14804 , lines per seconds: 861852.0670089165

BufferedReader 方式:

2017-09-28 14:27:03.374 [main] INFO  com.oppo.push.ts.dispatcher.imei2device.ImeiTransformerOfflineImpl - process imei file ends:/push/file/imei2device-local/20170928/imei2device-13 , lines :12758858 , cost :13001 , lines per seconds: 981375.1249903854
4

3 回答 3

5

事情就是这样:文件 IO 并不简单。

您必须记住,您的操作系统对将要发生的事情有巨大的影响。从这个意义上说:没有适用于所有平台上的所有 JVM 实现的可靠规则。

当您真的不得不担心最后一点性能时,在您的目标平台上进行深入分析是主要的解决方案。

除此之外,您将“性能”方面弄错了。含义:内存映射 IO 不会神奇地提高一次在应用程序中读取单个文件的性能。它的主要优势沿着这条路走:

如果您有多个进程以只读方式从同一个文件访问数据,那么 mmap 非常棒,这在我编写的那种服务器系统中很常见。mmap 允许所有这些进程共享相同的物理内存页面,从而节省大量内存。

(引用自this answer on using the C mmap()system call)

换句话说:您的示例是关于读取文件内容的。最后,操作系统仍然必须转向驱动器以从那里读取所有字节。含义:它读取光盘内容并将其放入内存中。当你一次这样做时......在此之上做一些“特殊”的事情真的没关系。相反——当你做“特殊”事情时,内存映射方法甚至可能更慢——因为与“普通”读取相比的开销。

回到我的第一条记录:即使你有 5 个进程读取同一个文件,内存映射方法也不一定更快。正如 Linux 可能想的那样:我已经将该文件读入内存,并且它没有改变 - 所以即使没有明确的“内存映射”,Linux 内核也可能会缓存信息。

于 2017-09-28T06:57:24.410 回答
3

内存映射并没有真正带来任何优势,因为即使您将文件批量加载到内存中,您仍然一次处理一个字节。如果您以适当大小的byte[]块处理缓冲区,您可能会看到性能提高。即使那样,该BufferedReader版本也可能表现更好或至少几乎相同。

您的任务的性质是按顺序处理文件。BufferedReader已经做得很好而且代码很简单,所以如果我必须选择我会选择最简单的选项。

另请注意,除了单字节编码外,您的缓冲区代码不起作用。一旦每个字符获得多个字节,它就会失败。

于 2017-09-28T07:20:47.187 回答
2

鬼猫是正确的。除了您的操作系统选择之外,其他可能会影响性能的因素。

  • 映射文件将对物理内存提出更高的要求。如果物理内存“紧张”,可能会导致分页活动和性能下降。

  • read如果您使用系统调用读取文件而不是将其映射到内存中,操作系统可能会使用不同的预读策略。预读(进入缓冲区缓存)可以使文件读取速度更快。

  • 的默认缓冲区大小BufferedReader和操作系统内存页面大小可能不同。这可能会导致磁盘读取请求的大小不同。(更大的读取通常会导致更大的吞吐量 I/O。至少在某个点上。)

您的基准测试方式也可能导致“伪影”。例如:

  • 第一次读取文件时,部分或全部文件的副本将落在缓冲区缓存中(在内存中)
  • 第二次读取同一个文件时,它的一部分可能还在内存中,并且明显的read时间会更短。
于 2017-09-28T08:06:08.713 回答