3

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 JavaCollection.size()的第 45 条中建议,如果它是上限的话,它是最快的选择。但它也适用于数组吗?从字节码(见下文)我可以看到arraylength每个周期保存一条指令(预优化)。

这个关于 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

}
4

1 回答 1

1

您可以自己轻松地测试它;如果你这样做了,你可能应该看看HotSpot 创建者自己提供的有关性能测试的这些提示。这个答案也可能有用。如果您决定测试这些实现,请告诉我们您的发现!

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

如果您确实发现您的代码运行速度太慢,请先分析,然后再优化。其他任何事情都为时过早。请记住,来自一个比我们任何人都聪明的人:

“过早的优化是万恶之源。” ——唐纳德·克努斯

编辑:由于您似乎对“我应该如何编写代码?”的好奇较少。以及更多的思想实验,我希望所有这些选项都以或多或少相同的速度运行。

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

当然,我的期望是无关紧要的。唯一知道的方法就是测试!但是,在测试时,请记住编写好的微基准测试很难

于 2013-07-07T19:57:20.550 回答