Java,为啥从 MappedByteBuffer 读取比从 BufferedReader 读取慢
Posted
技术标签:
【中文标题】Java,为啥从 MappedByteBuffer 读取比从 BufferedReader 读取慢【英文标题】:Java, why reading from MappedByteBuffer is slower than reading from BufferedReaderJava,为什么从 MappedByteBuffer 读取比从 BufferedReader 读取慢 【发布时间】:2017-09-28 06:53:26 【问题描述】:我试图从一个可能很大的文件中读取行。
为了获得更好的性能,我尝试使用映射文件。但是当我比较性能时,我发现映射文件的方式甚至比我从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
【问题讨论】:
我会更怀疑你写的mmap
的代码。 BufferedReader
方式简单明了,很难出错(如果您有基本经验)。映射一个文件并像你正在做的那样处理它看起来真的很无效。如果您分析应用程序,您可能会发现热点位于遍历每个字节并将其转换为 char
的方法中。
是的,我怀疑我的做法不是很好,有什么建议吗?
我注意到您的问题仍然是“开放的”:您没有接受答案。请查看并决定是否要accept 回答。或者让我知道我是否可以做些什么来增强我的输入以使其被接受。接受有助于未来的读者确定问题是否得到解决,并对花时间回答你的人表示感谢。
【参考方案1】:
事情就是这样:文件 IO 并不简单。
您必须记住,您的操作系统对即将发生的事情有巨大的影响。从这个意义上说:没有适用于所有平台上的所有 JVM 实现的可靠规则。
当您真的不得不担心最后一点性能时,在您的目标平台上进行深入分析是主要的解决方案。
除此之外,您将“性能”方面弄错了。含义:内存映射 IO 不会神奇地提高一次在应用程序中读取单个文件的性能。它的主要优势沿着这条路走:
如果您有多个进程以只读方式从同一个文件访问数据,那么mmap 非常棒,这在我编写的那种服务器系统中很常见。 mmap 允许所有这些进程共享相同的物理内存页面,从而节省大量内存。
(引用自 answer 关于使用 C 的 mmap()
系统调用)
换句话说:您的示例是关于读取文件内容的。最后,操作系统仍然必须转向驱动器从那里读取所有字节。含义:它读取光盘内容并将其放入内存中。当你第一次这样做时......在此之上做一些“特殊”的事情真的没关系。相反——当你做“特殊”的事情时,内存映射方法甚至可能更慢——因为与“普通”读取相比,开销。
回到我的第一条记录:即使您有 5 个进程读取同一个文件,内存映射方法也不一定更快。正如 Linux 可能想的那样:我已经将该文件读入内存,并且它没有改变 - 所以即使没有明确的“内存映射”,Linux 内核也可能会缓存信息。
【讨论】:
但是Linux应该很好地支持映射文件,我从映射缓冲区读取有什么问题吗? 当然支持!关键是内存映射文件 I/O 不一定更快。不管你怎么做。 @StephenC 很好的反馈。确实帮助我进一步增强了我的答案,以至于我只是忘了提及;-)【参考方案2】:内存映射并没有真正带来任何好处,因为即使您将文件批量加载到内存中,您仍然一次处理一个字节。如果您以适当大小的byte[]
块处理缓冲区,您可能会看到性能提升。即便如此,BufferedReader
版本的性能可能会更好,或者至少几乎相同。
您的任务的性质是按顺序处理文件。 BufferedReader
已经做得很好而且代码很简单,所以如果我必须选择我会选择最简单的选项。
另请注意,除了单字节编码外,您的缓冲区代码不起作用。一旦每个字符获得多个字节,它就会非常失败。
【讨论】:
【参考方案3】:GhostCat 是正确的。除了您的操作系统选择之外,其他可能会影响性能的因素。
映射文件将对物理内存提出更高的要求。如果物理内存“紧张”,可能会导致分页活动和性能下降。
如果您使用 read
系统调用读取文件,而不是将其映射到内存中,操作系统可能会使用不同的预读策略。预读(进入缓冲区缓存)可以使文件读取速度更快。
BufferedReader
的默认缓冲区大小和操作系统内存页面大小可能不同。这可能会导致磁盘读取请求的大小不同。 (更大的读取经常会导致更大的吞吐量 I/O。至少在某个点上。)
您的基准测试方式也可能导致“伪影”。例如:
第一次读取文件时,部分或全部文件的副本将放入缓冲区缓存(在内存中) 第二次读取同一个文件时,部分文件可能还在内存中,明显的read
时间会更短。
【讨论】:
没有注意到你的答案......但是,平等的思想,平等的思想;-)以上是关于Java,为啥从 MappedByteBuffer 读取比从 BufferedReader 读取慢的主要内容,如果未能解决你的问题,请参考以下文章
使用 java.nio.MappedByteBuffer 时防止 OutOfMemory
java大文件读写操作,java nio 之MappedByteBuffer,高效文件/内存映射
Java MappedByteBuffer.isLoaded()
Java NIO 利用通道完成文件复制(MappedByteBuffer)