9

我有一个算法,它目前分配了一个非常大的双精度数组,它经常更新和搜索。数组的大小为 N^2/2,其中 N 是算法运行的行数。为了与围绕算法的应用程序相关的目的,我还必须保留整个内容的副本。

当然,这对我的算法可以处理的行数施加了限制,因为我需要应对堆限制。到目前为止,我已经成功地要求使用该算法的人更新 -Xmx 设置以分配更多空间,并且效果很好。但是,我现在有一个真正的问题,我需要这个数组大于我可以放入内存的大小。

我已经计划改变我的算法以减轻这个大型阵列的必要性,并在该领域取得一些有希望的结果。然而,这是对流程的根本改变,需要做更多的工作才能达到我当前代码的高度抛光状态,该代码在生产中运行非常成功,并且已经运行了好几年。

所以,当我完善我的新算法时,我想延长现有算法的寿命,这意味着解决与分配我的巨大双精度数组相关的堆限制。

我的问题是处理它的最佳方法是什么?我应该使用 nio FileChannel 和 MappedByteBuffer,还是有更好的方法。如果我确实使用 nio 方法,与相同大小的内存数组相比,我应该预期会受到什么样的性能影响?

谢谢

4

7 回答 7

6

如果您开始用完可用内存,那么您可能很快也会开始用完可用的数组索引,数组的大小限制为Integer.MAX_VALUE,并且当使用双精度作为数组元素时,“仅”32GB 大小.

获得一台具有 32GB 内存的机器很昂贵,但可能不如修改算法和所有相关测试的时间那么昂贵。

但是,如果客户端运行到内存边缘,并且他们的数据集仍在增长,那么您现在就咬紧牙关,并进行更改以在任何给定时间使用更少的内存,因为它们无论如何,它可能很快就会超过一个数组。

假设数组有些稀疏地填充,您拥有的另一个选项是使用各种稀疏数组数据结构之一,尽管这些往往只有在您的数组小于 20% 的填充量时才有用。

编辑:既然您似乎已经研究了替代方案,那么 MappedByteBuffer 很可能是要走的路。显然这会对性能产生影响,但是如果您主要从阵列中进行顺序读取和写入,那么这应该不会太糟糕。如果您正在进行随机读写,那么这将变得非常慢非常快。或者非常缓慢非常缓慢......取决于你如何看待这些事情;-)

于 2009-12-16T22:52:57.000 回答
2

如果您在 PC 上运行,映射文件的页面大小可能为 4 KB。

所以问题真的从我开始将数据交换到磁盘开始,“我对现在是文件的 RAM 的随机访问有多随机”?

并且(...我可以吗?如果可以...)如何订购双打以最大化在下一次 4K 磁盘获取之前一起访问 4K 页面中的双打而不是在每个页面中一次访问几个的情况?

如果您使用标准 IO,您可能仍希望以块的形式读取和写入,但块可能会更小。扇区至少为 512 字节,磁盘集群更大,但考虑到每个 IO 都有内核往返开销,读取的最佳大小是多少?

很抱歉,恐怕您接下来的最佳步骤在很大程度上取决于您使用的算法和数据。

于 2009-12-16T23:02:07.927 回答
1

我对 Java 的 MappedByteBuffers 有很好的经验,并鼓励您更深入地了解它。它很可能让你不再处理这些-Xmx变化。请注意,如果您需要超过 2-4GB 的可寻址空间,则需要 64 位 CPU、操作系统和 JVM。

为了超越索引问题,您可以编写一个分页算法,正如我在 Java 中的有序(内存映射?)文件中的二进制搜索Integer.MAX_VALUE的相关答案中所做的那样。

于 2009-12-17T11:38:25.993 回答
0

您正在研究如何编写最能利用缓存(如 CPU 中的内存缓存)的软件。这很难做到正确,而“正确”的方法取决于您的算法是如何设计的。

那么,您的程序实际上在算法上做了什么?

于 2009-12-16T22:58:38.017 回答
0

您可以尝试将数组作为行存储在数据库表中,并使用存储过程对其进行更新和搜索。

另一个想法:

使用 B-Tree 作为阵列并在磁盘上保留一些叶子。确保并使 B-Tree 的节点为一个页面的大小或多个页面的大小。

于 2009-12-16T23:03:31.017 回答
0

如果问题是内存不足,简单的解决方案是使用更多内存升级硬件、增加 Java 堆大小和/或切换到 64-bi5t JVM。

另一方面,如果您违反了 Java 对数组大小的限制,您可以使用 ByteBuffer 路线,或者您可以切换到使用数组数组。后者是 Sun 建议的解决方法。

使用数组数组方法,您可以(理论上)处理N接近2**31. 在实践中,您的限制将取决于您拥有的物理内存量,以及使用您的 OS/JVM 组合可以解决的量。

于 2009-12-17T03:52:04.583 回答
0

请注意,某些操作系统对内存映射的支持比其他操作系统更好。

我很想这样做:

  1. 将所有数组获取/放置在对象接口后面(如果它们还没有的话),从而使您可以轻松更改实现。
  2. 使用 SoftReference 数组,其中每个 SoftReference 指向该行的双精度数组。当 GC 将数组踢出时,使用 ReferenceQueue 将数组保存到磁盘。当 get() 返回 null 时,从磁盘中检索。

您可能会发现这样可以更好地控制性能 - 可以根据需要调整 -Xmx。

于 2009-12-17T14:24:21.443 回答