内存映射文件的性能/稳定性——本地或MappedByteBuffer方式——与普通的FileOutputStream方式相比如何?

11
我支持一个遗留的Java应用程序,该应用程序使用纯文本的平面文件进行持久化。由于应用程序的性质,这些文件的大小每天可以达到数百MB,并且通常情况下限制应用程序性能的因素是文件IO。当前,该应用程序使用普通的java.io.FileOutputStream将数据写入磁盘。
最近,我们有几位开发人员声称,使用内存映射文件(由C/C++实现并通过JNI访问)将提供更高的性能。但是,FileOutputStream已经使用本地方法来执行其核心方法(即write(byte [])),因此在没有硬数据或至少有经验的证据的情况下,这似乎是一个脆弱的假设。
我有几个问题:
1. 这个说法真的正确吗?内存映射文件是否始终比Java的FileOutputStream提供更快的IO? 2. 通过FileChannel访问的MappedByteBuffer类是否提供与通过JNI访问的本地内存映射文件库相同的功能? MappedByteBuffer缺少什么可能导致您使用JNI解决方案? 3. 在生产应用程序中使用内存映射文件进行磁盘IO的风险是什么?也就是说,具有连续运行时间和最少重新启动(最多一个月)的应用程序。首选来自生产应用程序(Java或其他)的实际经验故事。
第三个问题很重要-我可以通过编写一个“玩具”应用程序来部分回答这个问题,该应用程序使用上述各种选项进行性能测试,但是通过发布到SO,我希望有真实的经验/数据可供参考。
【编辑】澄清 - 每天运行期间,应用程序创建多个文件,大小从100MB到1GB不等。总体而言,应用程序可能每天会写入多个GB的数据。
7个回答

6

内存映射I/O不会让你的磁盘运行更快。对于线性访问,它似乎有点毫无意义。

NIO映射缓冲区才是真正的东西(任何合理实现的典型警告)。

与其他NIO直接分配的缓冲区一样,这些缓冲区并不是普通内存,不能像GC一样高效回收。如果您创建了许多这样的缓冲区,您可能会发现在未耗尽Java堆之前已经耗尽了内存/地址空间。这显然是长时间运行进程时的一个问题。


该应用程序写入磁盘的频率比读取频率高得多(>99%)。您能否详细说明一下“对于线性访问,它似乎有点无意义”的含义 - 是否适用于追加操作? - noahlz
追加操作将是线性的(文件系统可能会使您的文件碎片化,但这应该是一个小问题)。 - Tom Hawtin - tackline

4
您可以通过检查数据在写入时的缓冲方式来加快速度。这往往是应用程序特定的,因为您需要了解预期的数据写入模式。如果数据一致性很重要,那么在此方面将会有权衡。
如果您只是从应用程序向磁盘中写入新数据,则内存映射I/O可能不会有太大帮助。根据您目前提供的内容,我没有看到您想要投资时间进行某些自定义编码的本地解决方案的任何理由。这似乎对您的应用程序来说过于复杂了。
如果您确信需要更好的I/O性能 - 或者仅在您的情况下需要更好的O性能,那么我建议您考虑硬件解决方案,例如经过调整的磁盘阵列。从业务角度来看,将更多硬件投入问题通常比花时间优化软件更具成本效益。而且通常更快实施和更可靠。
总的来说,在过度优化软件方面存在许多陷阱。您将向应用程序引入新类型的问题。您可能会遇到内存问题/ GC抖动,这将导致更多的维护/调整。最糟糕的部分是,在进入生产之前,许多这些问题都很难测试。
如果这是我的应用程序,我可能会坚持使用FileOutputStream并进行一些可能调整的缓冲。之后,我会使用备受推崇的解决方案 - 投入更多硬件来解决问题。

选择了这个答案来分散一下积分。此外,“或者在你的情况下只是O性能”这句话真的让我印象深刻。 - noahlz

3
根据我的经验,在实时和持久化使用情况下,内存映射文件的性能比纯文件访问要好得多。我主要在Windows上使用C++,但Linux的性能类似,而且您计划使用JNI,所以我认为这适用于您的问题。
关于内存映射文件构建的持久性引擎的示例,请参见Metakit。我在一个应用程序中使用它,其中对象是对内存映射数据的简单视图,引擎负责幕后处理所有映射内容。这既快速又内存高效(至少与传统方法相比,如先前版本使用的方法),并且我们免费获得了提交/回滚事务。
在另一个项目中,我必须编写多播网络应用程序。数据以随机顺序发送,以最小化连续数据包丢失的影响(结合FEC和阻塞方案)。此外,数据可能远远超出地址空间(视频文件大于2Gb),因此无法进行内存分配。在服务器端,文件部分会按需内存映射,并且网络层直接从这些视图中选择数据;因此,内存使用率非常低。在接收器端,无法预测数据包接收顺序,因此必须在目标文件上维护有限数量的活动视图,并且直接将数据复制到这些视图中。当数据包必须放置在未映射区域时,最旧的视图会被取消映射(并最终由系统刷新到文件中),并在目标区域上替换为新视图。性能非常出色,尤其是因为系统作为后台任务出色地处理了提交数据,实时约束轻松满足。
自那以后,我相信即使是最好的精心设计的软件方案也无法击败系统的默认I/O策略,内存映射文件是必须的,因为数据从不被分配(因此不消耗内存),而是动态映射到地址空间,并由系统的虚拟内存管理器管理,它总是比堆更快。因此,系统始终最优地使用内存,并在需要时在应用程序背后自动提交数据,而不会对其产生影响。
希望对您有所帮助。

1
关于第三点- 如果机器崩溃,并且有任何没有刷新到磁盘的页面,那么它们将会丢失。另一件事是地址空间的浪费 - 将文件映射到内存会消耗地址空间(并且需要连续的区域),在32位机器上有些受限。但你说了大约100MB - 所以应该不是问题。还有一件事-扩展mmap文件的大小需要进行一些工作。
顺便说一下,这个SO讨论也可以给你一些见解。

实际上它们会达到数百兆甚至高达一GB每个文件。有些应用程序的部署包括多个此类文件!我会修改以使其更清晰。 - noahlz

0

我做了一项研究,比较了将数据写入原始的ByteBuffer和写入MappedByteBuffer的性能。内存映射文件由操作系统支持,其写入延迟非常好,正如您在我的基准测试数字中所看到的那样。通过FileChannel执行同步写入大约慢20倍,这就是为什么人们一直在进行异步日志记录的原因。在我的研究中,我还举了一个例子,说明如何通过无锁和无垃圾队列实现异步日志记录,以获得接近原始ByteBuffer的终极性能。


我的发现是我不得不定期调用 force() 来确保我的更改被写入文件。因此,速度较慢。当然,这是几年前的事情了... - noahlz
除非你想保护自己免受操作系统崩溃的影响,否则不需要调用force()函数。 - TraderJoeChicago
答案中的链接无效。 - prayagupa
你能在这里发布结果吗?谢谢。 - Franz Wong

0

如上所述,请使用NIO(即新IO)。还有一个新的、新的IO即将推出。

正确使用RAID硬盘解决方案会对您有所帮助,但这可能会很麻烦。

我真的很喜欢压缩数据的想法。使用gzipoutputstream吧!如果CPU能跟得上,那么你的吞吐量将翻倍。现在标准的双核机器很可能可以利用,对吧?

-Stosh


0

如果你写的字节数越少,速度就会越快。那如果你通过gzipoutputstream过滤它呢?或者将数据写入ZipFiles或JarFiles中呢?


那么,您将交换IO操作的开销以换取数据编码/解码的开销。这需要一些实验来确定是否可行。 - noahlz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接