调用array.length的成本是多少

时间:2009-07-30 18:09:44

标签: java arrays premature-optimization

在为我们的应用程序中的每个循环更新for循环时,我遇到了很多这些“模式”:

for (int i = 0, n = a.length; i < n; i++) {
    ...
}

而不是

for (int i = 0; i < a.length; i++) {
    ...
}

我可以看到您获得了集合的性能,因为您不需要为每个循环调用 size()方法。但是对于数组??

所以问题出现了:array.length比常规变量更贵吗?

8 个答案:

答案 0 :(得分:68)

不,对array.length的调用是O(1)或持续时间操作。

由于.lengthpublic final成员array,因此访问权限并不比本地变量慢。 (与调用size()

等方法有很大不同

现代JIT编译器可能无论如何都会优化对.length的调用。

您可以通过查看OpenJDK中JIT编译器的源代码,或通过让JVM转储出JIT编译的本机代码并检查代码来确认这一点。

请注意,可能存在JIT编译器无法执行此操作的情况;例如

  1. 如果您正在调试封闭方法,或
  2. 如果循环体有足够的局部变量来强制注册溢出。

答案 1 :(得分:15)

午饭时我有点时间:

public static void main(String[] args) {
    final int[] a = new int[250000000];
    long t;

    for (int j = 0; j < 10; j++) {
        t = System.currentTimeMillis();
        for (int i = 0, n = a.length; i < n; i++) { int x = a[i]; }
        System.out.println("n = a.length: " + (System.currentTimeMillis() - t));

        t = System.currentTimeMillis();
        for (int i = 0; i < a.length; i++) { int x = a[i]; }
        System.out.println("i < a.length: " + (System.currentTimeMillis() - t));
    }
}

结果:

n = a.length: 672
i < a.length: 516
n = a.length: 640
i < a.length: 516
n = a.length: 656
i < a.length: 516
n = a.length: 656
i < a.length: 516
n = a.length: 640
i < a.length: 532
n = a.length: 640
i < a.length: 531
n = a.length: 641
i < a.length: 516
n = a.length: 656
i < a.length: 531
n = a.length: 656
i < a.length: 516
n = a.length: 656
i < a.length: 516

注意:

  1. 如果您反转测试,那么n = a.length显示比i < a.length快约一半,可能是由于垃圾收集(?)。
  2. 由于我250000000 OutOfMemoryError 270000000,我无法让{{1}}更大。
  3. 关键是,并且它是其他人一直在制作的,你必须运行Java内存,你仍然没有看到两种选择之间的速度有显着差异。把你的开发时间花在真正重要的事情上。

答案 2 :(得分:11)

我怀疑是否有任何重大差异,即使有,我敢打赌它可能在编译期间被优化掉了。当你尝试微观优化这样的事情时,你会浪费你的时间。首先使代码可读和正确,然后如果遇到性能问题,请使用分析器,然后担心选择更好的数据结构/算法(然后>担心优化您的探查器突出显示的部分。

答案 3 :(得分:7)

数组的长度存储为Java中数组的成员变量(与元素不同),因此获取该长度是一个常量时间操作,与从常规类中读取成员变量相同。许多较旧的语言(如C和C ++)不会将长度存储为数组的一部分,因此您需要在循环开始之前存储长度。你不必在Java中这样做。

答案 4 :(得分:4)

在这种情况下,为什么不进行反向循环:

for (int i = a.length - 1; i >= 0; --i) {
    ...
}

这里有2个微优化:

  • 循环反转
  • Postfix减量

答案 5 :(得分:3)

将它存储在变量中可能会稍快一些。但如果一个分析器指出它是一个问题,我会非常惊讶。

在字节码级别,使用arraylength字节码完成数组的长度。我不知道它是否比iload字节码慢,但是应该没有足够的区别来注意。

答案 6 :(得分:2)

这个答案来自C#的观点,但我认为这同样适用于Java。

在C#中,成语

for (int i = 0; i < a.length; i++) {    ...}

被认为是在数组上进行迭代,因此在循环中访问数组而不是每次访问数组时都会避免边界检查。

这可能会或可能不会被以下代码识别:

for (int i = 0, n = a.length; i < n; i++) {    ...}

n = a.length;

for (int i = 0; i < n; i++) {   ...}

编译器与我不知道的JITter执行了多少此优化,特别是如果它由JITter执行,我希望所有3都生成相同的本机代码。

然而,第一种形式也可以说是人们更具可读性,所以我想说的就是这样。

答案 7 :(得分:1)

Array.length是一个常量,JIT编译器应该在两个实例中都看到它。我希望在这两种情况下得到的机器代码都是相同的。至少对于服务器编译器而言。