【问题标题】:Why ArrayList performance differs if it is referenced as List?如果将 ArrayList 引用为 List,为什么它的性能会有所不同?
【发布时间】:2013-06-07 05:26:15
【问题描述】:

kjellkod's article 中提到,如果我们在接收List 作为参数的方法中传递ArrayList,那么我们会损失性能,因为ArrayList 实现了额外的RandomAccess 接口。 文章示例:

// SLOWER: as shown in http://ideone.com/1wnF1
private static void linearInsertion(Integer[] intArray, List<Integer> list) {
[...]
int list_size = list.size();
for (int i = 0; i < list_size; i++) {
if (integer.compareTo(list.get(i)) >= 0) { // ... more code

// FASTER: as shown in http://ideone.com/JOJ05
private static void linearInsertion(Integer[] intArray, ArrayList<Integer> list) {
[...]
int list_size = list.size();
for (int i = 0; i < list_size; i++) {
if (integer.compareTo(list.get(i)) >= 0) { // ... more code

来自参考:

鼓励通用列表算法检查给定列表是否 在应用算法之前是此接口的一个实例 如果将其应用于顺序,将提供较差的性能 访问列表,并在必要时更改其行为以保证 可接受的性能。

但是,如果我们真的在上述方法中传递 ArrayList 并检查list instanceof RandomAccess,那么这两种情况都是如此。 那么,我的第一个问题是为什么 Java VM 应该在第一种方法中将其解释为顺序列表?

我已经修改了文章中的测试来检查我机器上的这种行为。在ideone 上运行测试时,它显示的结果类似于 kjellkod 的结果。但是当我在本地运行它时,我得到了意想不到的结果,这与文章解释和我的理解相反。在我的例子中,ArrayList as List 迭代似乎比将它引用为 ArrayList 快 5-25%:

如何解释这种差异? 是否取决于架构或处理器内核数量?我的工作机器配置是 Windows 7 Professional x64、Intel Core i5-3470(4 核、4 线程)、16 GB RAM。

【问题讨论】:

  • 这篇文章对我来说没有意义......当你遍历一个列表时,调用的方法是使用的具体实现,而不是随机的低效实现。
  • 使用 -XX:LogCompilation 运行 java 将记录 JIT 优化,您可能会注意到一些差异。
  • BTW Gb = 千兆位,GB = 千兆字节。
  • 另请参阅此问题的答案:How do I write a correct micro-benchmark in Java?

标签: java time arraylist iteration random-access


【解决方案1】:

那么,我的第一个问题是为什么 Java VM 应该在第一种方法中将其解释为顺序列表?

JVM 没有顺序或随机访问列表的概念。 (除了标记接口)实现的开发者认识到 ArrayList 在 O(1) 时间而不是 O(n) 时间内执行随机访问查找。

它是否取决于架构或处理器内核的数量?

不,您会看到 -client 之间的区别,例如32 位 Windows 和 -server 例如任何 64 位 JVM。


我怀疑您是第二个运行 List 测试的。这可能会更快,因为代码在 JIT 和 CPU 缓存中都得到了更多警告。我建议您每个测试至少执行 3 次,先运行最长的测试,然后忽略第一次运行。

注意:对于 List,contains() 是 O(n),这就是为什么你的时间会增长 O(n^2)代码可能会令人困惑,因为您很容易受到优化和未优化的微妙之处的影响。通过比较已经相当优化的代码,您将获得更有意义的结果。

【讨论】:

  • 我尝试切换测试顺序,结果几乎一样
  • 您是否忽略了该 JVM 的第一次运行,即没有重新启动?
  • 是的,我试过这样做。此外,在我的测试中(以及原始文章中)在运行实际测试之前有一个“热身”部分
  • @RomanPetrenko 因此,您非常依赖 ArrayList.contains() 如何针对 Integer 类型进行优化。您实际拨打的电话并不重要。尝试使用其他类型如 Long 作为额外测试,您可能会看到 Integer 在病房后变得更慢。 ;)
  • 如果有预热,代码会根据预热中的使用情况进行优化。这意味着你预热代码的顺序很重要。
【解决方案2】:

虽然两种方法的代码相同,但理论上可能存在差异,因为在 JVM 级别调用接口方法与调用类方法不同。它们是 2 种不同的字节码操作:invokeinterfaceinvokevirtual。见http://bobah.net/d4d/source-code/misc/invokevirtual-vs-invokeinterface-performance-benchmark

【讨论】:

  • 尽管当只使用一种实现时,JVM 应该乐观地优化调用。
  • -1 invokeinterface 如果有任何差异,应该总是慢一些,因为它必须做更多的工作。这也是您的链接所声称的。 OP看到的不同之处在于它更快。即 OP 看到的行为与您的答案相反。
  • @PeterLawrey 实际上,似乎差异取决于工作机器,因为我的计算机在使用 List 时显示更好的性能,而 ideone 显示相反的结果
  • @RomanPetrenko 我怀疑您也没有完全相同版本的 Java。 ;)
猜你喜欢
  • 1970-01-01
  • 2012-04-08
  • 1970-01-01
  • 1970-01-01
  • 2014-01-09
  • 1970-01-01
  • 2019-04-14
  • 2019-08-18
  • 2019-04-25
相关资源
最近更新 更多