11

我支持使用平面文件(纯文本)进行持久性的遗留 Java 应用程序。由于应用程序的性质,这些文件的大小可以达到每天 100s MB,而应用程序性能的限制因素通常是文件 IO。目前,应用程序使用普通的 java.io.FileOutputStream 将数据写入磁盘。

最近,我们有几位开发人员断言,使用内存映射文件、以本机代码 (C/C++) 实现并通过 JNI 访问,将提供更高的性能。但是,FileOutputStream 已经将本机方法用于其核心方法(即 write(byte[])),因此在没有硬数据或至少没有轶事证据的情况下,这似乎是一个脆弱的假设。

我对此有几个问题:

  1. 这个说法真的是真的吗? 与 Java 的 FileOutputStream 相比,内存映射文件是否总是提供更快的 IO?

  2. 从 FileChannel 访问的 MappedByteBuffer 类是否提供与通过 JNI 访问的本机内存映射文件库相同的功能?MappedByteBuffer 缺少什么可能导致您使用 JNI 解决方案?

  3. 在生产应用程序中使用内存映射文件进行磁盘 IO 有哪些风险?也就是说,具有持续正常运行时间且重启次数最少的应用程序(最多每月一次)。来自生产应用程序(Java 或其他)的真实轶事优先。

问题 #3 很重要——我可以通过编写一个“玩具”应用程序来部分回答这个问题,该应用程序使用上述各种选项对 IO 进行性能测试,但是通过发布到 SO,我希望能够了解真实世界的轶事/数据.

[编辑] 澄清 - 每天运行,应用程序创建多个文件,大小范围从 100MB 到 1 gig。总的来说,该应用程序可能每天要写出多个演出数据。

4

7 回答 7

6

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

NIO 映射缓冲区是真实的(任何合理实现的通常警告)。

与其他 NIO 直接分配的缓冲区一样,缓冲区不是普通内存,不会被有效地 GC。如果您创建其中许多,您可能会发现内存/地址空间已用完,而 Java 堆却没有用完。对于长时间运行的进程,这显然是一个问题。

于 2009-02-11T16:10:49.717 回答
4

您可以通过检查数据在写入期间的缓冲方式来加快速度。这往往是特定于应用程序的,因为您需要了解预期的数据写入模式。如果数据一致性很重要,那么这里会有权衡。

如果您只是从应用程序将新数据写入磁盘,内存映射 I/O 可能不会有太大帮助。我看不出您有任何理由愿意在一些自定义编码的本机解决方案上投入时间。从您迄今为止提供的内容来看,您的应用程序似乎太复杂了。

如果您确定确实需要更好的 I/O 性能 - 或者在您的情况下只需要 O 性能,我会研究一种硬件解决方案,例如调整磁盘阵列。从业务角度来看,投入更多硬件解决问题通常比花时间优化软件更具成本效益。它通常也更快实施并且更可靠。

一般来说,软件的过度优化存在很多陷阱。您将为您的应用程序引入新类型的问题。您可能会遇到内存问题/GC 抖动,这将导致更多的维护/调整。最糟糕的是,其中许多问题在投入生产之前很难测试。

如果它是我的应用程序,我可能会坚持使用 FileOutputStream 和一些可能调整的缓冲。之后,我会使用久负盛名的解决方案,即投入更多硬件。

于 2009-02-11T17:52:20.520 回答
3

根据我的经验,内存映射文件在实时和持久性用例中的性能都比普通文件访问好得多。我主要在 Windows 上使用 C++,但 Linux 性能相似,而且您仍然打算使用 JNI,所以我认为它适用于您的问题。

有关基于内存映射文件构建的持久性引擎的示例,请参阅Metakit。我在一个应用程序中使用了它,其中对象是内存映射数据的简单视图,引擎负责幕后的所有映射内容。这既快速又节省内存(至少与以前版本使用的传统方法相比),而且我们免费获得了提交/回滚事务。

在另一个项目中,我不得不编写多播网络应用程序。数据以随机顺序发送,以尽量减少连续丢包的影响(结合 FEC 和阻塞方案)。此外,数据很可能超出地址空间(视频文件大于 2Gb),因此内存分配是不可能的。在服务器端,文件部分按需进行内存映射,网络层直接从这些视图中提取数据;因此,内存使用率非常低。在接收端,无法预测接收数据包的顺序,因此它必须在目标文件上维护有限数量的活动视图,并将数据直接复制到这些视图中。当必须将数据包放入未映射的区域时,最旧的视图被取消映射(并最终被系统刷新到文件中)并被目标区域上的新视图替换。性能非常出色,特别是因为系统在将数据作为后台任务提交方面做得很好,并且很容易满足实时约束。

从那时起,我确信即使是最好的精心设计的软件方案也无法击败系统默认的内存映射文件 I/O 策略,因为系统比用户空间应用程序更了解何时以及如何写入数据。另外,重要的是要知道在处理大数据时内存映射是必须的,因为数据永远不会被分配(因此会消耗内存)而是动态映射到地址空间,并由系统的虚拟内存管理器管理,即总是比堆快。因此,系统始终以最佳方式使用内存,并在需要时提交数据,在应用程序的背后而不影响它。

希望能帮助到你。

于 2009-02-11T15:52:49.153 回答
1

至于第 3 点 - 如果机器崩溃并且有任何页面没有刷新到磁盘,那么它们就会丢失。另一件事是地址空间的浪费——将文件映射到内存会消耗地址空间(并且需要连续的区域),而且在 32 位机器上它有点受限。但是您已经说过大约 100MB - 所以这应该不是问题。还有一件事 - 扩展 mmaped 文件的大小需要一些工作。

顺便说一下,这个 SO 讨论也可以给你一些见解。

于 2009-02-11T15:52:01.790 回答
0

如果你写更少的字节,它会更快。如果您通过 gzipoutputstream 过滤它会怎样,或者如果您将数据写入 ZipFiles 或 JarFiles 会怎样?

于 2009-02-12T14:38:17.837 回答
0

如上所述,使用 NIO(又名新 IO)。还有一个新的、新的 IO 出来了。

正确使用 RAID 硬盘驱动器解决方案会对您有所帮助,但这会很痛苦。

我真的很喜欢压缩数据的想法。去找 gzipoutputstream 老兄!如果 CPU 可以跟上,那将使您的吞吐量翻倍。您很可能可以利用现在标准的双核机器,嗯?

-斯托什

于 2009-04-21T01:59:13.993 回答
0

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

于 2012-12-02T20:12:34.437 回答