提问人:Simon 提问时间:12/17/2009 更新时间:12/18/2009 访问量:4699
我应该如何处理 Java 中非常大的数组?
How should I deal with a very large array in Java?
问:
我有一个算法,它目前分配了一个非常大的双精度数组,它经常更新和搜索。数组的大小为 N^2/2,其中 N 是算法运行的行数。我还必须保留整个事情的副本,用于与算法周围的应用程序相关的目的。
当然,这对我的算法可以处理的行数施加了限制,因为我有堆限制要应对。到目前为止,我已经要求使用该算法的人更新 -Xmx 设置以分配更多空间,并且效果很好。但是,我现在遇到了一个真正的问题,我需要这个数组大于内存的容量。
我已经计划改变我的算法,以减轻这个大数组的必要性,并在该领域取得一些有希望的结果。然而,这是对流程的根本性改变,需要更多的工作才能达到我当前代码的高度完善状态,该代码在生产中非常成功地运行并且已经运行了好几年。
因此,在我完善新算法的同时,我想延长现有算法的寿命,这意味着要解决与分配大量双精度数组相关的堆限制。
我的问题是处理它的最佳方法是什么?我应该使用 nio FileChannel 和 MappedByteBuffer,还是有更好的方法。如果我确实使用 nio 方法,与相同大小的内存中阵列相比,我应该期望获得什么样的性能影响?
谢谢
答:
如果您开始用完可用内存,那么您可能很快就会开始用完可用的数组索引,数组的大小限制为 ,并且当使用双倍作为数组元素时,大小“仅”为 32GB。Integer.MAX_VALUE
获得一台具有 32GB 内存的机器很昂贵,但可能不如修改算法的时间和所有相关的测试那么昂贵。
但是,如果客户端运行到内存的边缘,并且它们的数据集仍在增长,那么您现在咬紧牙关进行更改以能够在任何给定时间使用更少的内存是有意义的,因为它们可能很快就会超过数组。
假设数组填充有些稀疏,您拥有的另一个选项是使用各种稀疏数组数据结构之一,尽管这些结构往往仅在数组填充率低于 20% 时才有用。
编辑:由于您似乎已经研究了替代方案,因此MappedByteBuffer很可能是要走的路。显然,这将对性能产生影响,但是,如果您主要从阵列中执行顺序读取和写入操作,那么这应该不会太糟糕。如果你正在做随机的读取和写入,那么这将变得非常慢,非常快。或者很慢,很慢......取决于你如何看待这些事情;-)
评论
您正在研究如何编写最能利用缓存(如 CPU 中的内存缓存)的软件。这很难做对,而“正确”的方法取决于你的算法是如何设计的。
那么,你的程序在算法上实际上是做什么的呢?
评论
如果在电脑上运行,则映射文件的页面大小可能为 4 KB。
因此,问题实际上始于我是否开始将数据交换到磁盘,“我对RAM的随机访问有多随机”?
而且(...我可以吗,如果是这样......如何对双精度进行排序,以最大限度地利用在下一个 4K 磁盘获取之前同时访问 4K 页面中的双精度,而不是在每个页面中一次访问几个双精度?
如果您使用标准 IO,您可能仍然希望在块中读取和写入,但其他块可能会更小。扇区至少为 512 字节,磁盘集群更大,但考虑到每个 IO 都有内核往返开销,读取大小最好?
对不起,恐怕你最好的下一步在很大程度上取决于你使用的算法和数据。
评论
您可以尝试将数组存储为数据库表中的行,并使用存储的过程对其进行更新和搜索。
另一个想法:
使用 B 树作为阵列,并在磁盘上保留一些叶子。确保 B 树的节点大小为页面大小或多个页面大小。
评论
如果问题是内存不足,简单的解决方案是用更多内存升级硬件,增加 Java 堆大小和/或切换到 64-bi5t JVM。
另一方面,如果你正在反对 Java 对数组大小的限制,你可以沿着 ByteBuffer 路由,或者你可以切换到使用数组数组。后者是 Sun 建议的解决方法。
使用数组数组方法,您可以(理论上)处理接近 的值。在实践中,您的限制将取决于您拥有的物理内存量,以及使用操作系统/JVM 组合可以解决的内存量。N
2**31
我对 Java 的 MappedByteBuffers 总体上有很好的体验,并鼓励您更深入地了解它。它很可能允许您不再处理更改。请注意,如果您需要超过 2-4GB 的可寻址空间,则需要 64 位 CPU、操作系统和 JVM。-Xmx
为了解决索引问题,您可以编写一个分页算法,就像我在 Java 中对排序(内存映射?)文件中的二进制搜索的相关答案所做的那样。Integer.MAX_VALUE
评论
请注意,某些操作系统比其他操作系统对内存映射的支持更好。
我很想这样做:
- 将所有数组 get/put 放在对象接口后面(如果它们还没有),从而让您腾出时间轻松更改实现。
- 使用 SoftReferences 数组,其中每个 SoftReference 都指向该行的双精度数组。使用 ReferenceQueue 在 GC 将数组踢出时将数组保存到磁盘。当 get() 返回 null 时,从磁盘中检索。
您可能会发现这样可以更好地控制性能 - 可以根据需要调整 -Xmx。
评论