【问题标题】:List<Double> that uses RAM of double[]?List<Double> 使用双 [] 的 RAM?
【发布时间】:2011-12-21 06:36:59
【问题描述】:

Java 专家强调避免过早优化的重要性,而是专注于干净的 OO 设计。我试图在重写使用大量长元素(几百万)的程序的上下文中协调这一原则。似乎使用 ArrayList 会消耗大约 3 倍于原始 long 数组的内存,而且浪费这么多 RAM 对我来说似乎是一个合理的担忧。

这是基于我使用 MemoryTestBench 类 described here 进行的实验。我的测试和输出如下:

package memory;

import java.util.ArrayList;
import java.util.List;

public class ArrayListExperiment {

public static void main(String[] args) {

    ObjectFactory arrayList = new ObjectFactory() {
        public Object makeObject() {
            List<Long> temp = new ArrayList<Long>(1000);
            for (long i=0; i<1000; i++)
                temp.add(i);
            return temp;
        }
    };

    ObjectFactory primitiveArray = new ObjectFactory() {
        public Object makeObject() {
            long[] temp = new long[1000];
            for (int i=0; i<1000; i++)
                temp[i] = i;
            return temp;
        }
    };

    MemoryTestBench memoryTester = new MemoryTestBench();
    memoryTester.showMemoryUsage(primitiveArray);
    memoryTester.showMemoryUsage(arrayList);
}
}

和输出:

memory.ArrayListExperiment$2 produced [J which took 8016 bytes
memory.ArrayListExperiment$1 produced java.util.ArrayList which took 24968 bytes

我的问题是:我怎样才能获得 OO 列表的好处,同时仍然保留原始数组的小内存占用?我认为 guava 可能会提供答案,但是通过 API 浏览一下,我不清楚使用哪个类代替 ArrayList。

感谢您的任何建议。

【问题讨论】:

  • 这个 MemoryTestBranch 是否正确?看了文章没多久,看到了 System.gc() 之类的一些有趣的方法,一个接一个地看了几次。
  • 实际上,这听起来很对。 Double 需要一个参考,加上实际的原始双精度。在 64 位 JVM 中,您至少需要为 Double 支付两倍于 double 的成本,而且还有一些额外的开销。
  • rit,我没有资格评论那篇文章中方法的清洁度,但我相信内存结果是正确的
  • 你想做什么? ArrayList 总是会花费更多,因为它只包含对象(在您的情况下为 Double)。由于每个 Object/Double 确实是一个引用,因此每个 Double vs double 都会花费您额外的内存空间。 (在您的情况下为 1000)(参考 + 双精度的实际值)。
  • 为 Java 应用程序提供 1 GB 内存的情况并不少见。列表中有 300 万 Doubles:~ 0.07 GB。似乎内存占用很小:-)

标签: java data-structures guava


【解决方案1】:

我想你在 Guava 中寻找的是Doubles.asList

【讨论】:

  • 作为一名 Guava 开发人员,这确实是做事的方式,特别是因为正如你提到的,你的列表大小是固定的。
  • @LouisWasserman:Guava 版本是否提供Arrays.asList 没有的任何功能?
  • @TimothyJones: Arrays.asList 不适用于原始数组...传入double[],你会得到一个元素List&lt;double[]&gt;
  • @Jonah:显然会有一些性能差异,所以如果你真的,真的只需要你在 Java 中可能获得的绝对最快的速度(也许你需要)那么直接使用数组是唯一的选择。 Trove 选项也可能更快一些,因为它没有实现List,因此可以在其 API 中使用原始类型。顺便说一句,您真的应该使用System.nanoTime() 进行基准测试。
  • 同意。如果你需要 List 接口,Guava 是你能找到的最好的,如果没有,那么你可能应该直接使用 Trove 或数组。
【解决方案2】:

您可以考虑使用Trove,它提供对原始集合的支持,例如TDoubleArrayList 类:

一个可调整大小的、由数组支持的双原语列表。

编辑:这个类确实没有实现List,但这是Java 避免盒装原语的代价。 Guava's solution 是最通用的,而 Trove 最适合更极端的性能要求。

【讨论】:

  • trove 获胜。 RAM 使用率与 guava 相同,但 trove 的内存访问速度是原语的两倍:pastebin.com/Xyd6MbEq
  • ...另一方面,它没有实现List 接口;)
【解决方案3】:

我认为您正在寻找 FastUtil's DoubleArrayList - 它由原始数组支持。

如果您的收藏真的很大(大于 2^31 个元素),您可能还想看看他们的 BigArrays

【讨论】:

    【解决方案4】:

    编写您自己的使用基元数组的 ArrayList 实现。复制当前 ArrayList 代码并将内部 Object[] 替换为 double[]。

    应该是一个非常直接的复制和替换。

    编辑:内存消耗的最大危险将是“增长”。它会短暂占用至少两倍的空间,再加上你增加的额外空间。如果您无法预先调整数组的大小以避免这种情况,您可能需要考虑一个稍微不同的实现,它会随着时间的推移使用多个数组。更多关于插入和索引的数学知识,但应该不会太糟糕。

    【讨论】:

      【解决方案5】:

      Arrays.asList(T...) 可能就是您要找的。它返回由传递给它的数组支持的List&lt;T&gt; 实例。

      【讨论】:

      • 他仍然会支付创建 Double[] 的初始开销,而他似乎想避免这种情况。
      • 请记住,生成的列表将是固定大小的,因此您将无法在其上调用add() 而不会出错。如果您希望能够添加到列表中,那么您将需要一个常规的ArrayList,这会产生内存开销。
      • Jason,对于我的特定问题,列表大小是固定的并且事先知道,fwiw
      • @rfeak:你完全正确,这实际上并不能解决他的问题。我的错。
      • @Jonah:由于您实际上并未将Arrays.asList 的结果分配给该代码中的任何内容,因此您不会注意到结果是List&lt;double[]&gt; 而不是List&lt;Double&gt;
      【解决方案6】:

      这是一个很好的问题 - 性能与代码清洁度。我认为你有理由不那么关心干净的 OO 设计,而只是专注于为处理大量 long 数组的特定问题创建一个好的解决方案。如果这样做,将面向性能的代码保留在一个类/包中将最大限度地减少其对整体设计的影响。假设管理大量长列表只是更大应用程序的一小部分......

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多