【问题标题】:Java Performance - ArrayLists versus Arrays for lots of fast readsJava 性能 - ArrayLists 与 Arrays 的大量快速读取
【发布时间】:2010-11-14 01:12:37
【问题描述】:

我有一个程序,我需要在尽可能短的时间内(以毫秒为单位)对类似列表的对象进行 100,000 到 1,000,000 次随机访问读取,以用于类似元胞自动机的程序。我认为我正在使用的更新算法已经过优化(有效地跟踪活动单元等)。列表确实需要更改大小,但性能并不那么重要。所以我想知道使用 Arrays 而不是 ArrayLists 的性能是否足以在如此短的时间内处理如此多的读取时产生影响。目前,我正在使用 ArrayLists。

编辑: 我忘了说:我只是存储整数,所以另一个因素是使用整数包装类(在 ArrayLists 的情况下)与 ints(在数组的情况下)。有谁知道使用 ArrayList 是否实际上需要 3 个指针查找(一个用于 ArrayList,一个用于底层数组,一个用于 Integer->int),因为数组只需要 1 个(数组地址+特定的偏移量)诠释)? HotSpot 会优化额外的查找吗?这些额外的查找有多大意义?

编辑2: 另外,我忘了提到我还需要进行随机访问写入(写入,而不是插入)。

【问题讨论】:

  • 在 Java 中设计有意义的微基准非常困难。许多博客文章和论文“Statistically Rigorous Java Performance Evaluation”中已经描述了这些问题——如果你还没有,如果它真的很重要,你可能想四处搜索并阅读它。

标签: java performance arrays arraylist


【解决方案1】:

既然您已经提到您的数组实际上是原始类型数组,请考虑使用 Trove 库中的原始类型集合类。

@viking 报告了在他的应用程序中使用 Trove 的显着(十倍!)加速 - 请参阅 cmets。另一方面,Trove 集合类型与 Java 的标准集合 API 类型不兼容。所以 Trove(或类似的库)并不是所有情况下的答案。

【讨论】:

  • 我只想说,只需将 Trove ArrayList 替换为一个 Java 数组列表。拯救了我的一天。
【解决方案2】:

选项有:
1. 使用数组
2.使用内部使用数组的ArrayList

很明显 ArrayList 引入了一些开销(查看 ArrayList 源代码)。对于 99% 的用例,这种开销很容易被忽略。但是,如果您实现时间敏感算法并按索引从列表中读取数千万次,那么使用裸数组而不是列表应该会显着节省时间。运用常识。

请看这里:http://robaustin.wikidot.com/how-does-the-performance-of-arraylist-compare-to-array 我会亲自调整测试以避免编译器优化,例如我会将“j =”更改为“j +=”,然后在循环之后使用“j”。

【讨论】:

    【解决方案3】:

    基元要快得多(快得多)。总是。即使使用 JIT 转义分析等。跳过在 java.lang.Integer 中包装的东西。此外,跳过大多数 ArrayList 实现对 get(int) 所做的数组边界检查。大多数 JIT 可以识别简单的循环模式并删除循环,但如果您担心性能,则没有太多理由这样做。

    您不必自己编写原始访问代码 - 我敢打赌,您可以转而使用 COLT 库中的 IntArrayList - 请参阅 http://acs.lbl.gov/~hoschek/colt/ - “Colt 为高性能科学和技术提供了一组开源库用 Java 计算") - 只需几分钟的重构时间。

    【讨论】:

      【解决方案4】:

      如果您只创建一次列表并从中读取数千次,则 ArrayList 的开销可能很小,可以忽略不计。如果您要创建 数千个列表,请使用标准数组。循环中的对象创建很快就会变成二次方,这仅仅是因为实例化成员变量、调用继承链上的构造函数等所有开销。

      因此 - 并回答您的第二个问题 - 坚持使用标准 int 而不是 Integer 类。分析两者,您将很快(或者更确切地说,慢慢地)了解原因。

      【讨论】:

        【解决方案5】:

        使用ArrayList 而不是数组会产生开销,但它很可能很小。事实上,ArrayList 中有用的数据位可以存储在寄存器中,尽管您可能会使用更多(例如List 大小)。

        您在编辑中提到您正在使用包装器对象。这些确实有很大的不同。如果您通常重复使用相同的值,那么合理的缓存策略可能会很有用(Integer.valueOf 为 -128 到 128 提供相同的结果)。对于原语,原语数组通常会轻松胜出。

        作为一种改进,您可能希望确保相邻单元格在数组中往往是相邻的(您可以比使用space filling curve 的列的行做得更好)。

        【讨论】:

          【解决方案6】:

          Java 对其对象使用双重间接,因此它们可以在内存中移动并且其引用仍然有效,这意味着每次引用查找都等效于两次指针查找。这些额外的查找无法完全优化掉。

          也许更糟糕的是你的缓存性能会很糟糕。访问缓存中的值将比访问主内存中的值快很多倍。 (可能是 10 倍)如果你有一个 int[],你就知道这些值在内存中是连续的,因此很容易加载到缓存中。但是,对于 Integer[],Integers 单个对象可能会随机出现在您的内存中,并且更有可能是缓存未命中。此外,整数使用 24 字节,这意味着它们比 4 字节值更不可能适合您的缓存。

          如果您更新 Integer,这通常会导致创建的新对象比更新 int 值要多几个数量级。

          【讨论】:

          • 垃圾。多年来没有合理的 Java 实现使用句柄(IIRC,HotSpot 的早期版本确实重新引入了句柄,但那是在 1.2.2 左右——十年前最好的部分)。
          • 那么包装类的所有使用都被优化了吗?使用 Integer 而不是 int 的性能损失是什么?
          • 嗨@Tom,您可能是对的,但我会对如何实现这一点感兴趣。您是否知道任何文件解释了如何在没有双重间接的情况下实现这一目标?
          • 看看这个演示文稿,azulsystems.com/events/javaone_2009/session/… 第 65 页,也许我误解了它的意思。
          【解决方案7】:

          一种可能性是重新实现 ArrayList(这并不难),但通过锁定/释放调用周期公开支持数组。这为您的写入提供了便利,但为您预先知道的大量读/写操作公开了数组,这些操作不会影响数组大小。如果列表被锁定,则不允许添加/删除 - 只需获取/设置。

          例如:

            SomeObj[] directArray = myArrayList.lockArray();
            try{
              // myArrayList.add(), delete() would throw an illegal state exception
              for (int i = 0; i < 50000; i++){
                directArray[i] += 1;
              }
            } finally {
              myArrayList.unlockArray();
            }
          

          这种方法继续封装ArrayList的数组增长/等等...行为。

          【讨论】:

          • 这很聪明,也不太难。特别是因为我使用的是整数,重新实现可以通过使用原语而不是包装类来提高速度。大多数 jvm 是否优化了将包装类用于基元时的性能损失?
          • AFAIK,不,他们没有。事实上,我不认为他们可以。您在谈论“int[]”与“ArrayList”这一事实显着改变了答案。
          • @stephen C - 完全正确...由于 ArrayList 所需的对象包装器开销,数组在处理原语时显然会胜出。
          【解决方案8】:

          我会听从凯文的建议。

          如果您的程序要将其与带有数组的版本进行比较,请先使用列表并衡量您的性能。如果这能给您带来可衡量的性能提升,请使用数组,如果不保留列表,因为它们会让您的生活更轻松。

          【讨论】:

          • 是的,我一直在使用 ArrayLists,但是很多人一直在要求提高速度。
          • 同样的事情 :) 使用分析器来测量程序的速度并寻找真正的瓶颈,然后对其进行优化。我认识的很多人都推荐 Netbeans Profiler for Java。
          【解决方案9】:

          两者都试,但要衡量。

          您很可能可以在不更改所有代码的情况下将一些东西组合在一起以使内部循环使用数组。我怀疑 HotSpot 已经内联了方法调用,您不会看到任何性能提升。

          另外,尝试 Java 6 update 14 并使用 -XX:+DoEscapeAnalysis

          【讨论】:

            【解决方案10】:

            如果您不打算从这个结构中读取更多内容,那么请继续使用数组,因为按索引读取时会更快。

            但是,请考虑您将如何在其中获取数据,以及排序、插入、删除等是否是一个问题。如果是这样,您可能需要考虑其他基于集合的结构。

            【讨论】:

            • 添加到末尾和删除两者都需要发生,但是像同时添加n个元素这样的优化使得数组只需要复制一次是很容易的。哦,顺便说一句,我正在读写。
            【解决方案11】:

            ArrayLists 比 Arrays 慢,但大多数人认为差异很小。不过,在您的情况下可能很重要,因为您要处理成千上万的人。

            顺便复制一下:Array or List in Java. Which is faster?

            【讨论】:

            • 道歉;我检查了这个问题是否曾被问过并错过了。但是,他说的是存储数千个字符串,而我说的是一百万个左右的整数。
            【解决方案12】:

            数组会更快,因为它至少会跳过函数调用(即 get(i))。

            如果你有一个静态大小,那么数组就是你的朋友。

            【讨论】:

            • 函数调用将与现代 JVM 内联。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2022-01-05
            • 2017-09-05
            • 2012-07-15
            • 2012-05-31
            • 1970-01-01
            • 1970-01-01
            • 2013-10-28
            相关资源
            最近更新 更多