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.
}
(显然,这不会在大多数节目中产生很大的性能差异,但会让我感到幽默。): - )
向后循环并且很可怕。也许甚至缓存不友好?或者现代处理器能否在内存中检测到向后倾斜?
更短,我可以看到JIT编译器如何确定array
永远不会改变,因此length
是常量,因此它基本上可以用(3)替换它。但它做到了吗? (假设JVM是Oracle的Hotspot / Java 7。)
作为最快的选择,如果它是某个Collection.size()
的上限。但是它也适用于数组吗?从字节码(见下文)我可以看到每个周期保存一个arraylength
指令(预先优化)。
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
}
答案 0 :(得分:1)
你可以自己轻松测试一下;如果你这样做,你应该看看HotSpot创作者自己的these tips about performance testing。 This answer也可能有用。如果您决定测试这些实现,请告诉我们您的发现!
然而,总的来说,你不应该太担心这些事情。相反,专注于编写可读代码并完成工作。大多数时候,你会发现你的代码运行得足够快,没有任何“技巧”。现代硬件非常快,JIT也非常好。
如果您发现代码运行速度太慢,配置文件,则优化。其他任何事情都为时过早。请记住,从一个比我们任何人更聪明的人:
“过早优化是所有邪恶的根源。” - 唐纳德克努特
编辑:因为您对“我应该如何编写代码?”这一点似乎不太感兴趣。在思想实验方面,我希望所有这些选项都能以或多或少相同的速度运行。
这些循环都不可能调整分支预测器(无论如何,对于合理大小的数组)。我希望底层的JIT能够将任何重复的数组长度引用从(2)-style转换为(3)-style。在所有条件相同的情况下,(1)缓存性能并不比(2)或(3)差,仅仅因为它向后运行;对于给定的数组,相同的缓存行将像往常一样加载和命中(或不命中)。
当然,我的期望无关紧要。唯一知道的方法就是测试!但是,在测试时,请记住writing good microbenchmarks is hard。