迭代数组的哪个习惯用法最有效?

时间:2013-07-07 19:52:46

标签: java arrays performance for-loop

Java 5为我们提供了for-each循环,应该尽可能使用它。

但是如果你需要在块中使用数组的索引,那么最有效的习惯是什么?

// Option (1)
for (int i = array.length - 1; i >= 0; i--) {
    // Code.
}

// Option (2)
for (int i = 0; i < array.length; i++) {
    // Code.
}

// Option (3)
for (int i = 0, n = array.length; i < n; i++) {
    // Code.
}

(显然,这不会在大多数节目中产生很大的性能差异,但会让我感到幽默。): - )

  1. 向后循环并且很可怕。也许甚至缓存不友好?或者现代处理器能否在内存中检测到向后倾斜?

  2. 更短,我可以看到JIT编译器如何确定array永远不会改变,因此length是常量,因此它基本上可以用(3)替换它。但它做到了吗? (假设JVM是Oracle的Hotspot / Java 7。)

  3. Joshua Bloch在Effective Java的第45项中建议
  4. 作为最快的选择,如果它是某个Collection.size()的上限。但是它也适用于数组吗?从字节码(见下文)我可以看到每个周期保存一个arraylength指令(预先优化)。

  5. This question关于Dalvik虚拟机中的for循环,列出(1) - (3)最慢到最慢。然而,这些信息来自2008年,而Dalvik今天更加成熟,所以我认为情况仍然如此。

    查看上面示例生成的字节码,存在明显的差异:

    Compiled from "ForLoops.java"
    public class ForLoops extends java.lang.Object{
    static int[] array;
    
    public ForLoops();
      Code:
       0:   aload_0
       1:   invokespecial   #10; //Method java/lang/Object."<init>":()V
       4:   return
    
    public static void forLoop1();
      Code:
       0:   getstatic   #17; //Field array:[I
       3:   arraylength
       4:   iconst_1
       5:   isub
       6:   istore_0
       7:   goto    13
       10:  iinc    0, -1
       13:  iload_0
       14:  ifge    10
       17:  return
    
    public static void forLoop2();
      Code:
       0:   iconst_0
       1:   istore_0
       2:   goto    8
       5:   iinc    0, 1
       8:   iload_0
       9:   getstatic   #17; //Field array:[I
       12:  arraylength
       13:  if_icmplt   5
       16:  return
    
    public static void forLoop3();
      Code:
       0:   iconst_0
       1:   istore_0
       2:   getstatic   #17; //Field array:[I
       5:   arraylength
       6:   istore_1
       7:   goto    13
       10:  iinc    0, 1
       13:  iload_0
       14:  iload_1
       15:  if_icmplt   10
       18:  return
    
    }
    

1 个答案:

答案 0 :(得分:1)

你可以自己轻松测试一下;如果你这样做,你应该看看HotSpot创作者自己的these tips about performance testingThis answer也可能有用。如果您决定测试这些实现,请告诉我们您的发现!

然而,总的来说,你不应该太担心这些事情。相反,专注于编写可读代码并完成工作。大多数时候,你会发现你的代码运行得足够快,没有任何“技巧”。现代硬件非常快,JIT也非常好。

如果您发现代码运行速度太慢,配置文件,则优化。其他任何事情都为时过早。请记住,从一个比我们任何人更聪明的人:

  

“过早优化是所有邪恶的根源。” - 唐纳德克努特

编辑:因为您对“我应该如何编写代码?”这一点似乎不太感兴趣。在思想实验方面,我希望所有这些选项都能以或多或少相同的速度运行。

这些循环都不可能调整分支预测器(无论如何,对于合理大小的数组)。我希望底层的JIT能够将任何重复的数组长度引用从(2)-style转换为(3)-style。在所有条件相同的情况下,(1)缓存性能并不比(2)或(3)差,仅仅因为它向后运行;对于给定的数组,相同的缓存行将像往常一样加载和命中(或不命中)。

当然,我的期望无关紧要。唯一知道的方法就是测试!但是,在测试时,请记住writing good microbenchmarks is hard