在java中,我通常会做一个如下的for循环:
for (int i = 0; i < max; i++) {
something
}
但最近一位同事这样打字:
for (int i = 0; i < max; ++i) {
something
}
他说后者会更快。真的吗?
在java中,我通常会做一个如下的for循环:
for (int i = 0; i < max; i++) {
something
}
但最近一位同事这样打字:
for (int i = 0; i < max; ++i) {
something
}
他说后者会更快。真的吗?
不,这不是真的。您可以通过为每个循环计时大量迭代来衡量性能,但我相当肯定它们将是相同的。
这个神话来自++i
被认为比 C 更快的神话,i++
因为前者可以通过递增 i 然后返回它来实现。后者可以通过将 i 的值复制到临时变量、递增 i、然后返回临时变量来实现。第一个版本不需要制作临时副本,因此很多人认为它更快。但是,如果将表达式用作语句,则现代 C 编译器可以优化临时副本,以便在实践中没有区别。
这个问题需要一些 Java 字节码。考虑以下代码:
public class PostPre {
public static void main(String args[]) {
int n = 5;
loop1(n);
loop2(n);
}
public static void loop1(int n) {
for (int i = 0; i < n; i++) {}
}
public static void loop2(int n) {
for (int i = 0; i < n; ++i) {}
}
}
现在编译并反汇编它:
$ javac PostPre.java; javap -c PostPre.class
Compiled from "PostPre.java"
public class PostPre {
public PostPre();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_5
1: istore_1
2: iload_1
3: invokestatic #2 // Method loop1:(I)V
6: iload_1
7: invokestatic #3 // Method loop2:(I)V
10: return
public static void loop1(int);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iload_0
4: if_icmpge 13
7: iinc 1, 1
10: goto 2
13: return
public static void loop2(int);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iload_0
4: if_icmpge 13
7: iinc 1, 1
10: goto 2
13: return
}
loop1()
并loop2()
具有相同的字节码。
对于任何能力合理的优化器,它们将完全相同。如果您不确定,请查看输出字节码或对其进行分析。
即使是这样,我非常怀疑,你的同事真的应该有更好的东西来花时间学习,而不是如何优化循环表达式。
在你的环境中试试这个
public class IsOptmized {
public static void main(String[] args) {
long foo; //make sure the value of i is used inside the loop
long now = 0;
long prefix = 0;
long postfix = 0;
for (;;) {
foo = 0;
now = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
foo += i;
}
postfix = System.currentTimeMillis() - now;
foo = 0;
now = System.currentTimeMillis();
for (int i = 0; i < 1000000000; ++i) {
foo += i;
}
prefix = System.currentTimeMillis() - now;
System.out.println("i++ " + postfix + " ++i " + prefix + " foo " + foo);
}
}
}
我的给我
i++ 1690 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1600 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1611 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1600 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1692 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
所以即使不是那么多,我认为还是有区别的
不,根本没有区别。
这来自 C++,但即使在这种情况下也不会有任何区别。不同之处在于 i 是一个对象。i++ 必须制作对象的附加副本,因为它必须返回项目的原始未更改值,而 ++i 可以返回更改的对象,因此保存副本。
在具有用户定义对象的 c++ 中,副本的成本可能很高,因此绝对值得记住。正因为如此,人们也倾向于将它用于 int 变量,因为无论如何它都一样好......
使用“javap -c YourClassName”反编译并查看结果并从中做出决定。通过这种方式,您可以看到编译器在每种情况下实际做了什么,而不是您认为它做了什么。通过这种方式,您还可以看到为什么一种方式比另一种方式更快。
它不会更快。带有 JIT 的编译器和 JVM 将使这些微不足道的差异变得肉馅。
您可以使用通常的循环优化技术来获得速度优势,例如展开(如果适用)。
在 Java 中应该没有区别——任何现代编译器*在这两种情况下都应该生成相同的字节码(只是一个iinc
),因为增量表达式的结果没有被直接使用。
还有第三个选项,仍然是相同的字节码*:
for (int i = 0; i < max; i += 1) {
something
}
* 用 Eclipse 的编译器测试
在 Java 中没有这样的区别。Java 机器会干扰代码,无论您编写 ++i 还是 i++,它都会被转换为字节码,以完全相同的指令集。
但是在 C/C++ 中存在巨大差异,如果您不使用任何优化标志,那么您的循环可能会慢 3 倍。
使用诸如 -O/-O3 之类的优化标志将强制编译器使输出汇编代码更简单(在大多数情况下),因此更快(在大多数情况下)。
即使它会更快,在 HotSpot 时代也没有人关心。JIT 做的第一件事是删除javac 所做的所有优化。之后,一切都留给 JIT 以使其快速运行。