在Java的for循环中,++i比i++快吗?

41

在Java中,我通常会像下面这样使用for循环:

for (int i = 0; i < max; i++) {
   something
}

但最近我的同事这样输入:

for (int i = 0; i < max; ++i) {
   something
}

他说后者会更快。这是真的吗?


7
我认为你会很难衡量最终的差异。通常通过优化循环内部的部分可以取得更好的结果 ;) - Felice Pollano
2
可能是这样,但是微小到你甚至都不会注意到。这种微观优化完全没有用处。 - Rafe Kettler
1
@PeeHaa:两个循环运行相同的次数。我尝试了max = 10,它们都从0到9运行i。 - Mnementh
@Linus Kleen,这些问题获得大量浏览并不奇怪 - 程序员倾向于喜欢技巧和调整,并且认为他们正在编写强大而快速的代码... 大多数程序员学会了编码,然后必须学会不要过度优化他们的代码。 - jball
请参见:https://dev59.com/xnI-5IYBdhLWcg3wTWj4 - user85421
11个回答

63

不,这不是真的。你可以通过计时每个循环的大量迭代来衡量性能,但我相当确定它们将是相同的。

这个谣言来自C语言,其中++i被认为比i++更快,因为前者可以通过增加i然后返回它来实现,而后者可能会通过将i的值复制到临时变量中,增加i,然后返回临时变量来实现。第一个版本不需要进行临时拷贝,所以许多人认为它更快。然而,如果该表达式用作语句,则现代C编译器可以优化掉临时拷贝,因此实际上没有区别。


12
现代编译器确实会对此进行优化,但如果你正在使用C++,并且i是一个对象(比如迭代器),而这些运算符不是内联的话,++i会比i++更快。 - fbafelipe
另外,如果你参加工作面试,应该说它更快:)但是如果你开始在C++中使用迭代器,那么就没有it++这样的东西。你将一遍遍地撞头,不停地收到相同的愚蠢错误消息。像我一样x) - Barney Szabolcs
1
@BarnabasSzabolcs,后缀递增可以被定义为迭代器,只是你一直没有使用正确的迭代器(或者错误的迭代器,这取决于你的观点)。 - JAB

46

这个问题需要一些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()具有相同的字节码。


9
对于任何一个能力足够的优化器,它们都是完全相同的。如果您不确定,请查看输出的字节码或对其进行分析。

6
即使是这样,我非常怀疑,你的同事真的应该有更好的事情来学习,而不是如何优化循环表达式。

16
看,妈妈!如果我只使用字母表前半部分的字母作为变量名,我的程序运行得更快了!去重构机器! - Rafe Kettler
1
@Rafe Kettler 如果这是真的,那就有点可怕了。 :) - biziclop
3
许多编译器中,较短的变量名使用的内存较少,这是让人感到害怕的事情。我们可能会整天沉迷于微小的技巧,但我的处理器上并不会有 7.3 亿个晶体管,这并非偶然。 - Rafe Kettler
1
@Rafe Kettler 这不是我听过的最疯狂的理论,那个奖应该颁给这个想法:如果硬盘上有更多的信息,它的重量会更重。 - biziclop
@biziclop 如果你在上面放一张报纸,也许会有所改善。 - mike

2

请在您的环境中尝试此操作。

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

所以即使差距不大,我认为还是有区别的。

1

在Java中,不存在这样的区别。 Java虚拟机解释代码,无论你写++i还是i ++,都将转换为相同的指令集的字节码。

但是在C / C ++中存在巨大的差异,如果你不使用任何优化标志,那么你的循环速度可能会慢到3倍。

使用像-O / -O3这样的优化标志将强制编译器使输出汇编代码更简单(在大多数情况下),因此更快(在大多数情况下)。


1

不,完全没有任何区别。

这来自C ++,但即使在那里,在这种情况下也没有任何区别。当i是一个对象时,有区别之处是i ++必须创建该对象的另一个副本,因为它必须返回项目的原始未更改值,而++ i可以返回已更改的对象,因此节省了一个副本。

在使用用户定义对象的C ++中,复制的成本可能很高,因此值得记住。由于这个原因,人们倾向于在int变量中使用它,因为无论如何它都一样好...


1
使用“javap -c YourClassName”反编译并查看结果,然后根据结果做出决定。这样可以看到编译器在每种情况下实际执行的操作,而不是您认为的操作。这样还可以看到为什么一种方法比另一种方法更快。

1

不会更快。编译器和带有JIT的JVM将轻松处理这种微不足道的差异。

如果适用,您可以使用常规循环优化技术来获得速度优势,例如展开。


0
在Java中,两种写法应该没有区别 - 任何现代编译器*都应该生成相同的字节码(只是一个iinc),因为增量表达式的结果并没有被直接使用。
还有第三种选项,仍然是相同的字节码*:
for (int i = 0; i < max; i += 1) {
   something
}

* 已使用Eclipse编译器测试


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接