Java 阵列和阵列列表位置访问性能

Java 阵列和阵列列表位置访问性能,java,arrays,performance,arraylist,Java,Arrays,Performance,Arraylist,我刚刚阅读了以下代码示例: 什么原因 j=INT_数组[i]; 比……快三倍 j=数组\列表。获取(i) 我知道ArrayList在内部使用数组。所以我想知道 详细地说,这次添加了哪些额外操作(调用方法、强制转换、其他JVM注意事项等) 提前感谢。性能将在很大程度上取决于所涉及的虚拟机以及各种其他考虑因素。这篇文章开头的笼统陈述让我怀疑,作者对JVM上的性能如何变化几乎一无所知,测试代码的其余部分证实了这一点。它没有进行足够长的测试,也没有使用任何JVM预热期或任何类似的时间。哦,当测试Arr

我刚刚阅读了以下代码示例:

什么原因 j=INT_数组[i]; 比……快三倍 j=数组\列表。获取(i)

我知道ArrayList在内部使用数组。所以我想知道 详细地说,这次添加了哪些额外操作(调用方法、强制转换、其他JVM注意事项等)

提前感谢。

性能将在很大程度上取决于所涉及的虚拟机以及各种其他考虑因素。这篇文章开头的笼统陈述让我怀疑,作者对JVM上的性能如何变化几乎一无所知,测试代码的其余部分证实了这一点。它没有进行足够长的测试,也没有使用任何JVM预热期或任何类似的时间。哦,当测试
ArrayList
版本时,它使用
INT\u ARRAY.length
,这意味着JIT优化的一个潜在来源被删除。真的不是一篇好文章

但是,很容易考虑ARRAYList.GET()超出正常数组访问的事情:

  • 空性检查(查看ArrayList引用是否为非空)。这是对数组本身的空性检查的补充,这对于数组和ArrayList都是必需的
  • 可能是虚拟方法间接寻址,这取决于JIT是否已成功内联调用
  • 边界检查-与数组访问不同,因为列表的大小通常小于数组的长度
但最终,单个方法调用的性能并不重要。重要的是这在您的实际用例中是否重要。应用程序是否花费大部分时间从集合中获取单个元素?它是否在本文所示的循环中这样做,而这种循环不做任何其他事情,因此在某种情况下可能受益于额外的JIT优化


微基准标记很有趣,但在为您提供有用信息时,您需要意识到它的局限性。

我的理解是JVM有特定的操作码用于处理阵列。性能差异很可能是方法调用的开销等。为什么不编写一个简单的测试用例,并使用
javad
来查看代码到底编译成了什么。这应该会给你一个想法。

如果不检查发布的链接,并且假设数组列表的速度比你所说的慢3倍,那么速度差可能会随着JVM的不同而变化,有几件事可能会影响返回值的速度。根据执行测试时有效的控制变量,该文章的结果可能会有所不同。考虑到在完成操作之前已经完成了各种检查,一个集合的速度应该不会令人惊讶地慢一点。添加到arraylist,例如调用

 public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
        Object oldData[] = elementData;
        int newCapacity = (oldCapacity * 3)/2 + 1;
            if (newCapacity < minCapacity)
        newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
    }
    }
public-capacity(int-minCapacity){
modCount++;
int oldCapacity=elementData.length;
如果(最小容量>旧容量){
对象oldData[]=elementData;
int newCapacity=(旧容量*3)/2+1;
if(新容量<最小容量)
新容量=最小容量;
//minCapacity通常接近大小,因此这是一个胜利:
elementData=Arrays.copyOf(elementData,newCapacity);
}
}
这清楚地表明,存在检查,数据被复制和替换。
因此,简单地说,没有一个答案,测试条件可能会影响结果。

它可能有助于找出导致差异的原因(在他特定的处理器、JVM、操作系统等上)来查看生成的字节码

对于readFromArrayList:

   6:   goto    25
   9:   getstatic       #47; //Field ARRAY_LIST:Ljava/util/List;
   12:  iload_3
   13:  invokeinterface #116,  2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
   18:  checkcast       #17; //class java/lang/Integer
   21:  astore_0
   22:  iinc    3, 1
   25:  iload_3
   26:  getstatic       #25; //Field INT_ARRAY:[Ljava/lang/Integer;
   29:  arraylength
   30:  if_icmplt       9
对于readFromArray:

   6:   goto    18
   9:   getstatic       #25; //Field INT_ARRAY:[Ljava/lang/Integer;
   12:  iload_3
   13:  aaload
   14:  astore_0
   15:  iinc    3, 1
   18:  iload_3
   19:  getstatic       #25; //Field INT_ARRAY:[Ljava/lang/Integer;
   22:  arraylength
   23:  if_icmplt       9

我不知道我是否购买了“三次”差异,但无论存在什么差异,都可以追溯到op#13:aaload(用于数组)与invokeinterface和checkcast(用于数组列表)之间的差异。

测试写得很糟糕。你不能从中学到很多东西

确实,像
get
这样的访问器需要一些时间,但例如sunjvm可以将其中的许多访问器优化到几乎为零。特别是,
ArrayList
get实际上不需要花费任何额外的时间

下面是一个基准测试(用Scala编写,但使用Java的数组和
ArrayList
),演示了当您实际使用数组中的(所有)值时,差异有多小:

object ArraySpeed {
  def ptime[A](f: => A) = {
    val t0 = System.nanoTime
    val ans = f
    printf("Elapsed: %.3f seconds\n",(System.nanoTime-t0)*1e-9)
    ans
  }

  val a = Array.range(0,1000000).map(x => new java.lang.Integer(x))
  val b = new java.util.ArrayList[java.lang.Integer]
  a.foreach(x => b.add(x))

  var j = 0

  def jfroma = {
    var i=0
    while (i<1000000) {
      j += a(i).intValue
      i += 1
    }
    j
  }

  def jfromb = {
    var i=0
    while (i<1000000) {
      j += b.get(i).intValue
      i += 1
    }
    j
  }

  def main(args: Array[String]) {
    for (i <- 1 to 5) {
      ptime(for (j <- 1 to 100) yield jfroma)
      ptime(for (j <- 1 to 100) yield jfromb)
      println
    }
  }
}

Scala字节码与Java字节码非常相似,所以这是一个相当公平的比较。(scala命令只是一个包装器,可以在类路径中使用正确的库调用
java

没有最好的方法。根据不同的网络情况,我有3种不同的方法: 1.当我必须在低成本操作的情况下进行大量的循环时-是的,改进数据访问可以为您提供良好的优化百分比。 2.如果情况与1相同。但由于内部操作繁重,优化访问只是一个非常小的优化,更好的方法是优化对象中的字段和操作。 3.大量的周期和繁重的计算,没有更多的优化-使一些'坏习惯'编程。
例如:不是每次都返回新值,而是将工作变量传递给函数并返回其中的结果。这听起来很愚蠢,但减少了变量创建和内存碎片。在我的情况下,这给了我15-25%的时间。原因?减少GC调用。没有时间调用构造函数。

00036的区别,你是认真的吗?它是关于查找时间而不是插入时间。+1表示世界“空”。。。今天我完全要找个借口用这个。我问这个问题是从理论的角度,而不是从实践的角度。我要问的是这两者之间的一个主要区别:它们都是堆上的对象,并且都在末尾使用数组。@Yaron:但是您要开始使用fr
$ scalac ArraySpeed.scala
$ scala ArraySpeed
Elapsed: 0.324 seconds   // This is direct array access
Elapsed: 0.378 seconds   // This is ArrayList

Elapsed: 0.326 seconds
Elapsed: 0.389 seconds

Elapsed: 0.355 seconds
Elapsed: 0.349 seconds

Elapsed: 0.323 seconds
Elapsed: 0.333 seconds

Elapsed: 0.318 seconds
Elapsed: 0.331 seconds